diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-04 17:06:28 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-04 17:06:28 +0200 |
| commit | c843d34f56c207abcf4b93e424125eea2dc601e0 (patch) | |
| tree | 415dc2d9be4be31e290e1dd1dedb0b630c5e8fc6 /Map/Business | |
| parent | ed10ac191df473c92c4fec495aafa7f569d108c8 (diff) | |
Upgrade to Swift 6
Diffstat (limited to 'Map/Business')
| -rw-r--r-- | Map/Business/Constants.swift | 19 | ||||
| -rw-r--r-- | Map/Business/MapParser/MapParser.swift | 215 | ||||
| -rw-r--r-- | Map/Business/MapParser/Strategies/BlockerParserStrategy.swift | 40 | ||||
| -rw-r--r-- | Map/Business/MapParser/Strategies/EdgeParserStrategy.swift | 46 | ||||
| -rw-r--r-- | Map/Business/MapParser/Strategies/GroupParserStrategy.swift | 51 | ||||
| -rw-r--r-- | Map/Business/MapParser/Strategies/NoteParserStrategy.swift | 45 | ||||
| -rw-r--r-- | Map/Business/MapParser/Strategies/OpportunityParserStrategy.swift | 47 | ||||
| -rw-r--r-- | Map/Business/MapParser/Strategies/StageParserStrategy.swift | 38 | ||||
| -rw-r--r-- | Map/Business/MapParser/Strategies/VertexParserStrategy.swift | 50 | ||||
| -rw-r--r-- | Map/Business/Stage.swift | 209 |
10 files changed, 760 insertions, 0 deletions
diff --git a/Map/Business/Constants.swift b/Map/Business/Constants.swift new file mode 100644 index 0000000..1c6f54d --- /dev/null +++ b/Map/Business/Constants.swift @@ -0,0 +1,19 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. + +struct Constants { + static let kMaxZoom = 2.0 + static let kMinZoom = 0.1 +} diff --git a/Map/Business/MapParser/MapParser.swift b/Map/Business/MapParser/MapParser.swift new file mode 100644 index 0000000..e94de77 --- /dev/null +++ b/Map/Business/MapParser/MapParser.swift @@ -0,0 +1,215 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import CoreGraphics +import Foundation + +struct MapParser { + static func parse(content: String) -> ParsedMap { + + let parsers = [ + AnyMapParserStrategy(NoteParserStrategy()), + AnyMapParserStrategy(VertexParserStrategy()), + AnyMapParserStrategy(EdgeParserStrategy()), + AnyMapParserStrategy(BlockerParserStrategy()), + AnyMapParserStrategy(OpportunityParserStrategy()), + AnyMapParserStrategy(StageParserStrategy()), + AnyMapParserStrategy(GroupParserStrategy()), + ] + let builder = MapBuilder() + + let lines = content.split(whereSeparator: \.isNewline) + + for (index, line) in lines.enumerated() { + for parser in parsers { + if parser.canHandle(line: String(line)) { + let (type, object) = parser.handle( + index: index, line: String(line), vertices: builder.vertices) + builder.addObjectToMap(type: type, object: object) + break + } + } + } + + return builder.build() + } +} + +// MARK: - Types + +struct MapParsingPatterns { + static let vertex = try! NSRegularExpression( + pattern: + "^([^\\(\\[\\]]*?)[\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(?:\\[(.*?)\\])?[\\s]*$", + options: [.caseInsensitive, .anchorsMatchLines]) + static let edge = try! NSRegularExpression( + pattern: "^(.+?)[\\s]*-([->])[\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) + static let blocker = try! NSRegularExpression( + pattern: "^\\[(Blocker)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) + static let opportunity = try! NSRegularExpression( + pattern: "^\\[(Evolution)\\][\\s]*(.+)[\\s]+([-+])[\\s]*([0-9]+.?[0-9]*)", + options: [.caseInsensitive, .anchorsMatchLines]) + static let note = try! NSRegularExpression( + pattern: + "^\\[(Note)\\][\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(.*)", + options: [.caseInsensitive, .anchorsMatchLines]) + static let stage = try! NSRegularExpression( + pattern: "^\\[(I{1,3})\\][\\s]*([0-9]+.?[0-9]*)", + options: [.caseInsensitive, .anchorsMatchLines]) + static let group = try! NSRegularExpression( + pattern: "^\\[(Group)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) +} + +struct ParsedMap { + let vertices: [Vertex] + let edges: [MapEdge] + let blockers: [Blocker] + let opportunities: [Opportunity] + let notes: [Note] + let stages: [CGFloat] + let groups: [[Vertex]] + + static let empty: ParsedMap = ParsedMap( + vertices: [], edges: [], blockers: [], opportunities: [], notes: [], stages: defaultDimensions, + groups: []) +} + +struct Vertex: Identifiable, Hashable { + let id: Int + let label: String + let position: CGPoint + var shape: VertexShape = .circle +} + +struct Note { + let id: Int + let position: CGPoint + let text: String +} + +enum VertexShape: String { + case circle + case square + case triangle + case x +} + +struct MapEdge { + let id: Int + let origin: CGPoint + let destination: CGPoint + let arrowhead: Bool +} + +struct Blocker { + let id: Int + let position: CGPoint +} + +struct Opportunity { + let id: Int + let origin: CGPoint + let destination: CGPoint +} + +struct StageDimensions { + let index: Int + let dimensions: CGFloat +} + +private let defaultDimensions: [CGFloat] = [ + 25.0, + 50.0, + 75.0, +] + +// MARK: - MapParserStrategy protocol + +protocol MapParserStrategy { + func canHandle(line: String) -> Bool + func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) +} + +struct AnyMapParserStrategy: MapParserStrategy { + + private let base: MapParserStrategy + + init<T: MapParserStrategy>(_ base: T) { + self.base = base + } + + func canHandle(line: String) -> Bool { + return base.canHandle(line: line) + } + func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { + return base.handle(index: index, line: line, vertices: vertices) + } +} + +// MARK: - Map Builder + +class MapBuilder { + var vertices: [String: Vertex] = [:] + private var edges: [MapEdge] = [] + private var blockers: [Blocker] = [] + private var opportunities: [Opportunity] = [] + private var notes: [Note] = [] + private var stages: [CGFloat] = defaultDimensions + private var groups: [[Vertex]] = [] + + func addObjectToMap(type: Any.Type, object: Any) { + if type == Vertex.self { + let vertex = object as! Vertex + vertices[vertex.label] = vertex + } + + if type == MapEdge.self { + let edge = object as! MapEdge + edges.append(edge) + } + + if type == Blocker.self { + let blocker = object as! Blocker + blockers.append(blocker) + } + + if type == Opportunity.self { + let opportunity = object as! Opportunity + opportunities.append(opportunity) + } + + if type == Note.self { + let note = object as! Note + notes.append(note) + } + + if type == StageDimensions.self { + let stageDimensions = object as! StageDimensions + stages[stageDimensions.index] = stageDimensions.dimensions + } + + if type == [Vertex].self { + let group = object as! [Vertex] + groups.append(group) + } + } + + func build() -> ParsedMap { + let mappedVertices = vertices.map { label, vertex in return vertex } + return ParsedMap( + vertices: mappedVertices, edges: edges, blockers: blockers, opportunities: opportunities, + notes: notes, + stages: stages, groups: groups) + } +} diff --git a/Map/Business/MapParser/Strategies/BlockerParserStrategy.swift b/Map/Business/MapParser/Strategies/BlockerParserStrategy.swift new file mode 100644 index 0000000..4bd2ce4 --- /dev/null +++ b/Map/Business/MapParser/Strategies/BlockerParserStrategy.swift @@ -0,0 +1,40 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import Foundation + +struct BlockerParserStrategy: MapParserStrategy { + private let regex = MapParsingPatterns.blocker + + func canHandle(line: String) -> Bool { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + return matches.count > 0 && matches[0].numberOfRanges == 3 + } + + func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + + let match = matches[0] + let vertexA = String(line[Range(match.range(at: 2), in: line)!]) + + if let vertex = vertices[vertexA] { + let blocker = Blocker(id: index, position: vertex.position) + return (Blocker.self, blocker) + } + + return (NSObject.self, NSObject()) // No matching object + } +} diff --git a/Map/Business/MapParser/Strategies/EdgeParserStrategy.swift b/Map/Business/MapParser/Strategies/EdgeParserStrategy.swift new file mode 100644 index 0000000..cc29b49 --- /dev/null +++ b/Map/Business/MapParser/Strategies/EdgeParserStrategy.swift @@ -0,0 +1,46 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import Foundation + +struct EdgeParserStrategy: MapParserStrategy { + private let regex = MapParsingPatterns.edge + + func canHandle(line: String) -> Bool { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + return matches.count > 0 && matches[0].numberOfRanges == 4 + } + + func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + + let match = matches[0] + let arrowhead = String(line[Range(match.range(at: 2), in: line)!]) == ">" + let vertexA = String(line[Range(match.range(at: 1), in: line)!]) + let vertexB = String(line[Range(match.range(at: 3), in: line)!]) + + if let origin = vertices[vertexA] { + if let destination = vertices[vertexB] { + let edge = MapEdge( + id: index, origin: origin.position, destination: destination.position, + arrowhead: arrowhead) + return (MapEdge.self, edge) + } + } + + return (NSObject.self, NSObject()) // No matching object + } +} diff --git a/Map/Business/MapParser/Strategies/GroupParserStrategy.swift b/Map/Business/MapParser/Strategies/GroupParserStrategy.swift new file mode 100644 index 0000000..96de365 --- /dev/null +++ b/Map/Business/MapParser/Strategies/GroupParserStrategy.swift @@ -0,0 +1,51 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import Foundation + +struct GroupParserStrategy: MapParserStrategy { + private let regex = MapParsingPatterns.group + + func canHandle(line: String) -> Bool { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + return matches.count > 0 && matches[0].numberOfRanges == 3 + } + + func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + + let match = matches[0] + var groupVertices: [Vertex] = [] + let vertexIdString = String(line[Range(match.range(at: 2), in: line)!]) + let vertexIds = vertexIdString.split(separator: ",", omittingEmptySubsequences: true).map( + String.init + ).map({ vertexId in + vertexId.trimmingCharacters(in: .whitespacesAndNewlines) + }) + + for vertexId in vertexIds { + if let vertex = vertices[vertexId] { + groupVertices.append(vertex) + } + } + + if groupVertices.count > 0 { + return ([Vertex].self, groupVertices) + } + + return (NSObject.self, NSObject()) + } +} diff --git a/Map/Business/MapParser/Strategies/NoteParserStrategy.swift b/Map/Business/MapParser/Strategies/NoteParserStrategy.swift new file mode 100644 index 0000000..d06c768 --- /dev/null +++ b/Map/Business/MapParser/Strategies/NoteParserStrategy.swift @@ -0,0 +1,45 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import Foundation + +struct NoteParserStrategy: MapParserStrategy { + private let regex = MapParsingPatterns.note + + func canHandle(line: String) -> Bool { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + return matches.count > 0 && matches[0].numberOfRanges == 5 + } + + func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + + let match = matches[0] + let text = String(line[Range(match.range(at: 4), in: line)!]) + let xString = String(line[Range(match.range(at: 2), in: line)!]) + let yString = String(line[Range(match.range(at: 3), in: line)!]) + let x = CGFloat(truncating: NumberFormatter().number(from: xString) ?? 0.0) + let y = CGFloat(truncating: NumberFormatter().number(from: yString) ?? 0.0) + + let note = Note( + id: index, + position: CGPoint(x: x, y: y), + text: text + ) + + return (Note.self, note) + } +} diff --git a/Map/Business/MapParser/Strategies/OpportunityParserStrategy.swift b/Map/Business/MapParser/Strategies/OpportunityParserStrategy.swift new file mode 100644 index 0000000..25e6cc4 --- /dev/null +++ b/Map/Business/MapParser/Strategies/OpportunityParserStrategy.swift @@ -0,0 +1,47 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import Foundation + +struct OpportunityParserStrategy: MapParserStrategy { + private let regex = MapParsingPatterns.opportunity + + func canHandle(line: String) -> Bool { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + return matches.count > 0 && matches[0].numberOfRanges == 5 + } + + func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + + let match = matches[0] + let multiplier = CGFloat( + String(line[Range(match.range(at: 3), in: line)!]) == "-" ? -1.0 : 1.0) + let vertex = String(line[Range(match.range(at: 2), in: line)!]) + let opportunityString = String(line[Range(match.range(at: 4), in: line)!]) + let opportunity = CGFloat( + truncating: NumberFormatter().number(from: opportunityString) ?? 0.0) + + if let origin = vertices[vertex] { + let destination = CGPoint( + x: origin.position.x + opportunity * multiplier, y: origin.position.y) + let opportunity = Opportunity(id: index, origin: origin.position, destination: destination) + return (Opportunity.self, opportunity) + } + + return (NSObject.self, NSObject()) // No matching object + } +} diff --git a/Map/Business/MapParser/Strategies/StageParserStrategy.swift b/Map/Business/MapParser/Strategies/StageParserStrategy.swift new file mode 100644 index 0000000..6e4a341 --- /dev/null +++ b/Map/Business/MapParser/Strategies/StageParserStrategy.swift @@ -0,0 +1,38 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import Foundation + +struct StageParserStrategy: MapParserStrategy { + private let regex = MapParsingPatterns.stage + + func canHandle(line: String) -> Bool { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + return matches.count > 0 && matches[0].numberOfRanges == 3 + } + + func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + + let match = matches[0] + let stage = String(line[Range(match.range(at: 1), in: line)!]) + let dimensionsString = String(line[Range(match.range(at: 2), in: line)!]) + let dimensions = CGFloat(truncating: NumberFormatter().number(from: dimensionsString) ?? 0.0) + + let stageDimensions = StageDimensions(index: stage.count - 1, dimensions: dimensions) + return (StageDimensions.self, stageDimensions) + } +} diff --git a/Map/Business/MapParser/Strategies/VertexParserStrategy.swift b/Map/Business/MapParser/Strategies/VertexParserStrategy.swift new file mode 100644 index 0000000..0ca6fc1 --- /dev/null +++ b/Map/Business/MapParser/Strategies/VertexParserStrategy.swift @@ -0,0 +1,50 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import Foundation + +struct VertexParserStrategy: MapParserStrategy { + private let regex = MapParsingPatterns.vertex + + func canHandle(line: String) -> Bool { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + return matches.count > 0 && matches[0].numberOfRanges >= 4 + } + + func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + + let match = matches[0] + let key = String(line[Range(match.range(at: 1), in: line)!]) + let xString = String(line[Range(match.range(at: 2), in: line)!]) + let yString = String(line[Range(match.range(at: 3), in: line)!]) + let x = CGFloat(truncating: NumberFormatter().number(from: xString) ?? 0.0) + let y = CGFloat(truncating: NumberFormatter().number(from: yString) ?? 0.0) + + var vertex = Vertex( + id: index, + label: key, + position: CGPoint(x: x, y: y) + ) + + if let range = Range(match.range(at: 4), in: line) { + let shapeString = String(line[range]) + vertex.shape = VertexShape(rawValue: shapeString.lowercased()) ?? .circle + } + + return (Vertex.self, vertex) + } +} diff --git a/Map/Business/Stage.swift b/Map/Business/Stage.swift new file mode 100644 index 0000000..4f737a1 --- /dev/null +++ b/Map/Business/Stage.swift @@ -0,0 +1,209 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +struct Stage { + let i: String + let ii: String + let iii: String + let iv: String + + static func stages(_ type: StageType) -> Stage { + switch type { + case .general: + return Stage( + i: "Genesis", ii: "Custom", iii: "Product (+rental)", iv: "Commodity (+utility)") + case .practice: + return Stage( + i: "Novel", ii: "Emerging", iii: "Good", iv: "Best") + case .data: + return Stage( + i: "Unmodelled", ii: "Divergent", iii: "Convergent", iv: "Modelled") + case .knowledge: + return Stage( + i: "Concept", ii: "Hypothesis", iii: "Theory", iv: "Accepted") + case .ubiquity: + return Stage( + i: "Rare", ii: "Slowly Increasing", iii: "Rapidly Increasing", + iv: "Widespread in the applicable market / ecosystem") + case .certainty: + return Stage( + i: "Poorly Understood / exploring the unknown", + ii: "Rapid Increase In Learning / discovery becomes refining", + iii: "Rapid increase in use / increasing fit for purpose", + iv: "Commonly understood (in terms of use)") + case .publicationTypes: + return Stage( + i: + "Describe the wonder of the thing / the discovery of some marvel / a new land / an unknown frontier", + ii: + "Focused on build / construct / awareness and learning / many models of explanation / no accepted forms / a wild west", + iii: + "Maintenance / operations / installation / comparison between competing forms / feature analysis", + iv: "Focused on use / increasingly an accepted, almost invisible component") + case .market: + return Stage( + i: "Undefined Market", + ii: "Forming Market / an array of competing forms and models of understanding", + iii: "Growing Market / consolidation to a few competing but more accepted forms", + iv: "Mature Market / stabilised to an accepted form") + case .knowledgeManagement: + return Stage( + i: "Uncertain", ii: "Learning on use / focused on testing prediction", + iii: "Learning on operation / using prediction / verification", iv: "Known / accepted") + case .marketPerception: + return Stage( + i: "Chaotic (non-linear) / domain of the \"crazy\"", ii: "Domain of \"experts\"", + iii: "Increasing expectation of use / domain of \"professionals\"", + iv: "Ordered (appearance of being linear) / trivial / formula to be applied") + case .userPerception: + return Stage( + i: "Different / confusing / exciting / surprising / dangerous", + ii: "Leading edge / emerging / unceirtanty over results", + iii: "Increasingly common / disappointed if not used or available / feeling left behind", + iv: "Standard / expected / feeling of shock if not used") + case .perceptionInIndustry: + return Stage( + i: "Future source of competitive advantage / unpredictable / unknown", + ii: "Seen as a scompetitive advantage / a differential / ROI / case examples", + iii: "Advantage through implementation / features / this model is better than that", + iv: "Cost of doing business / accepted / specific defined models") + case .focusOfValue: + return Stage( + i: "High future worth but immediate investment", + ii: "Seeking ways to profit and a ROI / seeking confirmation of value", + iii: + "High profitability per unit / a valuable model / a feeling of understanding / focus on exploitation", + iv: + "High volume / reducing margin / important but invisible / an essential component of something more complex" + ) + case .understanding: + return Stage( + i: "Poorly Understood / unpredictable", + ii: "Increasing understanding / development of measures", + iii: "Increasing education / constant refinement of needs / measures", + iv: "Believed to be well defined / stable / measurable") + case .comparison: + return Stage( + i: "Constantly changing / a differential / unstable", + ii: "Learning from others / testing the water / some evidential support", + iii: "Competing models / feature difference / evidential support", + iv: "Essential / any advantage is operational / accepted norm") + case .failure: + return Stage( + i: "High / tolerated / assumed to be wrong", + ii: "Moderate / unsurprising if wrong but disappointed", + iii: + "Not tolerated / focus on constant improvement / assumed to be in the right direction / resistance to changing the model", + iv: "Surprised by failure / focus on operational efficiency") + case .marketAction: + return Stage( + i: "Gambling / driven by gut", ii: "Exploring a \"found\" value", + iii: "Market analysis / listening to customers", iv: "Metric driven / build what is needed") + case .efficiency: + return Stage( + i: "Reducing the cost of change (experimentation)", ii: "Reducing cost of waste (Learning)", + iii: "Reducing cost of waste (Learning)", iv: "Reducing cost of deviation (Volume)") + case .decisionDrivers: + return Stage( + i: "Heritage / culture", ii: "Analyses & synthesis", iii: "Analyses & synthesis", + iv: "Previous Experience") + case .behavior: + return Stage( + i: "Uncertain when to use", ii: "Learning when to use", iii: "Learning through use", + iv: "Known / common usage") + } + } + + static func title(_ type: StageType) -> String { + switch type { + case .general: + return "Activities" + case .practice: + return "Practice" + case .data: + return "Data" + case .knowledge: + return "Knowledge" + case .ubiquity: + return "Ubiquity" + case .certainty: + return "Certainty" + case .publicationTypes: + return "Publication Types" + case .market: + return "Market" + case .knowledgeManagement: + return "Knowledge Management" + case .marketPerception: + return "Market Perception" + case .userPerception: + return "User Perception" + case .perceptionInIndustry: + return "Perception In Industry" + case .focusOfValue: + return "Focus Of Value" + case .understanding: + return "Understanding" + case .comparison: + return "Comparison" + case .failure: + return "Failure" + case .marketAction: + return "Market Action" + case .efficiency: + return "Efficiency" + case .decisionDrivers: + return "Decision Drivers" + case .behavior: + return "Behavior" + } + } +} + +enum StageType: String, CaseIterable, Identifiable { + case general + case practice + case data + case knowledge + + case ubiquity + case certainty + case publicationTypes + + case market + case knowledgeManagement + case marketPerception + case userPerception + case perceptionInIndustry + case focusOfValue + case understanding + case comparison + case failure + case marketAction + case efficiency + case decisionDrivers + + case behavior + + var id: String { self.rawValue } + + static let types: [StageType] = [.general, .practice, .data, .knowledge] + static let characteristics: [StageType] = [.ubiquity, .certainty, .publicationTypes] + static let properties: [StageType] = [ + .market, .knowledgeManagement, .marketPerception, .userPerception, + .perceptionInIndustry, .focusOfValue, .understanding, .comparison, .failure, + .marketAction, .efficiency, .decisionDrivers, + ] + static let custom: [StageType] = [.behavior] +} |