Skip to content
Snippets Groups Projects
Commit db6120b1 authored by martiivGylden's avatar martiivGylden
Browse files

Starting code rewrite : )

parent ecd8df9c
Branches
No related tags found
No related merge requests found
#File contains constants from the CSV export format of the entity relationship tool
# Last updated: 06.05.2024
#Generic fields for all rows in the CSV file
Id = "Id" # The id of the entity
Name = "Name" # ! Identifier, can be used to distinguish diagram components
# Constants for text areas
textArea1 = "Text Area 1"
textArea2 = "Text Area 2"
textArea3 = "Text Area 3"
......@@ -30,93 +28,62 @@ textArea23 = "Text Area 23"
textArea24 = "Text Area 24"
textArea25 = "Text Area 25"
#Generic fields for all rows in the CSV file
Id = "Id" # The id of the entity
Name = "Name" # ! Identifier, can be used to distinguish diagram components
#Mapped fields for Threat component
Threat = "Threat" # The threat
ThreatValue = textArea1 # The threat
ThreatID = textArea2 # The id of the threat
ThreatIDValue = textArea3 # Value
ThreatSource = textArea6 # The threat source
ThreatSourceValue = textArea7 # Value
# Lines
From = "Line Source"
To = "Line Destination"
Likelihood = textArea6 # The likelihood of the threat
LikelihoodValue = textArea7 # Value
ThreatDescription = textArea4 # The description of the threat
ThreatDescriptionValue = textArea5 # Value
#Mapped fields for Threat component
Threat = "Threat" # The threat
ThreatComponent = textArea1 # The threat
Vulnerability = textArea10 # The vulnerability
VulnerabilityValue = textArea11 # Value
ThreatMethodID = textArea3 # Value
ThreatName = textArea5 # The name of the threat
ThreatDescription = textArea7 # Value
ThreatSource = textArea9 # Value
Likelihood = textArea11 # Value
#Consequence
Consequence = "Consequence" # The consequence score of the area -> Text area 6
ConsequenceValue = textArea1 # The consequence
ConsequenceID = textArea2 # The id of the consequence
ConsequenceIDValue = textArea3 # Value
ConsequenceDescription = textArea4 # The description of the consequence
ConsequenceDescriptionValue = textArea5 # Value
ConsequenceComponent = textArea1 # The consequence
ConsequenceScore = textArea6 # The consequence score of the area
ConsequenceScoreValue = textArea7 # Value
ConsequenceID = textArea3 # Value
ConsequenceName = textArea5 # The name of the consequence
ConsequenceDescription = textArea7 # The description of the consequence
ConsequenceScore = textArea9 # The consequence score of the area
AffectedComponent = textArea8 # The affected component
AffectedComponentValue = textArea9 # Value
#Attack
Attack = "Attack" # The attack
AttackValue = textArea1 # The attack
Attack = "Cyber Attack" # The attack
AttackComponent = textArea1 # The attack
AttackType = textArea3
AttackDescription = textArea5
AttackType = textArea2 # The type of attack
AttackTypeValue = textArea3 # Value
# Relationship
relationship = "Relationship"
typeField = textArea3
relationshipDescription = textArea5
AttackedComponent = textArea4 # The attacked component
AttackedComponentValue = textArea5 # Value
# ER components
erName = textArea1
erID = textArea3
erDescription = textArea5
erType = textArea7
AttackDescription = textArea6 # The description of the attack
AttackDescriptionValue = textArea7 # Value
#Dynamics
Dynamics = "Dynamics" # The dynamics
ThreatDynamic = "Threat" # The threat dynamics
ConsequenceDynamic = "Consequence" # The consequence dynamics
AttackDynamic = "Attack" # The attack dynamics
ERDynamic = "ER" # The ER dynamics
BowtieDynamicType = textArea3 # The bowtie dynamics
# ! Note all metric fields use ID and name as the fields for the metric
# ! The ID will be used to link them in the dynamic matrix
# Lines
From = "Line Source"
To = "Line Destination"
# Table
# ? Column names
Er_ID = "ER ID"
Bowtie_ID = "Bowtie ID"
Metric_ID = "Metric ID"
Metric_Name = "Metric Name"
indicatorID = "Indicator ID"
indicatorName = "Indicator name"
Value = "Value"
Measure_date = "Measure Date"
Measure_date = "Measurement date"
Frequency = "Frequency"
Measurement_guide = "Measurement guide"
# Column mapping
ErColumn = textArea1
BowTieColumn = textArea2
MetricColumn = textArea3
MetricNameColumn = textArea10
ValueColumn = textArea13
MeasureDateColumn = textArea16
FrequencyColumn = textArea19
MeasurementGuideColumn = textArea22
# Column locations
# ! Edge case: The first entries have irregular placement and needs specialized values
Scale = "Scale"
Interpretation = "Interpretation of measurement"
......@@ -10,22 +10,68 @@ class Threat:
Fields are parsed from the exported CSV file
Threats have associated consequences, dynamic metrics and a related attack
"""
def __init__(self, id, componentID, threatSource, description, likelihood, vulnerability) -> None:
def __init__(self,componentID ,id, name , description, threatSource, likelihood) -> None:
self.id = id # The id field in the ER model
self.componentID = componentID # The component ID field provided by LucidChart
self.name = name # The name of the component
self.description = description # The description of the component
self.threatSource = threatSource # The threat source, source of the threat ...
self.likelihood = likelihood # The likelihood of the threat to occur
self.vulnerability = vulnerability # The vulnerability associated with the threat
self.linkedDynamics = [] # List of linked dynamics components
self.linkedConsequences = [] # List of linked consequences
self.linkedIndicators: dict[int, str] = {} # Dictironary of linked indicators ID, Reasonnig
def __str__(self) -> str:
return f"Threat: {self.id}, {self.description}, {self.threatSource}, {self.likelihood}, {self.vulnerability}"
return f"Threat: {self.id}, {self.name}, {self.linkedIndicators}"
def findIndicatorsThreat(df:pd.DataFrame, diagram: component.Diagram, self):
"""
Links the indicators for the threat to the threat object and updates the metrics dictionary with the indicators
"""
componentId = self.componentID # Define the ID of the component
lines = df.loc[df['Name']=='Line'] # Find all lines in the diagram
lineSource = lines.loc[lines[const.From] == componentId] # Find the line where the component is the source
lineDestination = lines.loc[lines[const.To] == componentId] # ! The component SHOULD be source however, to error handle we need to check for destination as well
if lineSource.isnull().values.any() == True: # Checks if the threat is the source or destination
lineOne = lineSource
else:
lineOne = lineDestination
if lineOne[const.From].item() == componentId: # If the component is the source
lineTwo = df.loc[df[const.Id] == lineOne[const.To].item()] # Find the component associated with the line
else:
lineTwo = df.loc[df[const.Id] == lineOne[const.From].item()] # Find the component associated with the line
sourceComponent = df.loc[df[const.Id]==lineTwo[const.From].item()] # Defines the source of the line connecting threats/consequences and attacks
destinationComponent = df.loc[df[const.Id]==lineTwo[const.To].item()] # Defines the destination of the line connecting threats/consequences and attacks
if componentType == const.ThreatDynamic: # * If the component is a threat
if sourceComponent[const.textArea1].item() == const.Threat: # Checks if source or destination is the threat
self.associatedThreat = sourceComponent
self.associatedAttack = destinationComponent
else:
self.associatedThreat = destinationComponent
self.associatedAttack = sourceComponent
elif componentType == const.ConsequenceDynamic: # * If the component is a consequence
if sourceComponent[const.textArea1].item() == const.Consequence:
self.associatedConsequence = sourceComponent
self.associatedAttack = destinationComponent
else:
self.associatedConsequence = destinationComponent
self.associatedAttack = sourceComponent
# ! After parsing the different components related to the dynamics component,
# ! the metrics in the component are added to the global metric list and local metric list
self.linkMetric(df, metricsDict)
class Consequence:
"""_summary_
......@@ -33,16 +79,19 @@ class Consequence:
Consequences are linked to threats and attacks and also contain their own dynamic metrics
"""
def __init__(self, id, componentID, description, consequence, affectedComponents) -> None:
def __init__(self, componentID, id, name, description, consequence) -> None:
self.id = id # The id field in the ER model
self.componentID = componentID # The component ID field provided by LucidChart
self.name = name # The name of the component
self.description = description # The description of the component
self.consequence = consequence # The consequence of the component
self.affectedComponents = affectedComponents # List of affected components
self.linkedIndicators: dict[int, str] = {} # Dictironary of linked indicators ID, Reasonnig
def __str__(self) -> str:
return f"Consequence: {self.id}, {self.description}, {self.consequence}, {self.affectedComponents}"
return f"Consequence: {self.id}, {self.name}, {self.consequence}, {self.linkedIndicators}"
class Attack:
......@@ -51,31 +100,36 @@ class Attack:
Attacks are linked to consequences and threats and contain their own dynamic metrics
"""
def __init__(self, id, componentID, type, component, description) -> None:
self.id = id # The id field in the ER model
def __init__(self, componentID, type, description) -> None:
self.componentID = componentID # The component ID field provided by LucidChart
self.type = type # The type of the attack
self.component = component # The component related to the attack (linked by ID)
self.description = description # The description of the component
self.associatedThreats = [] # List of associated threats linked by componentID
self.associatedConsequences = [] # List of associated consequences linked by componentID
self.linkedDynamics = [] # List of linked dynamics components
self.associatedThreats: list[int] = [] # List of associated threats linked by ID
self.associatedConsequences: list[int] = [] # List of associated consequences linked by ID
def __str__(self) -> str:
return f"Attack: {self.id}, {self.description}, {self.attackType}"
return f"Attack: {self.description}, {self.attackType}"
class ERComponent:
def __init__(self, name, id, description, type) -> None:
self.name = name
self.id = id
self.description = description
self.type = type
self.linkedIndicators:dict[int, str] = {}
@dataclass
class Diagram():
threats: dict[Threat]
consequences : dict[Consequence]
attacks : dict[Attack]
dynamics : dict[DynamicComponent]
metrics : dict[Metric]
erComponents : dict[str]
# Dicttionaries of threats, consequences, attacks, indicators and ER components
threats: dict[int,Threat]
consequences : dict[int,Consequence]
attacks : dict[int, Attack] # ! The ID of the attack is the componentID not ID!
indicators : dict[int, Indicator]
erComponents : dict[int, ERComponent]
def __init__(self) -> None:
self.threats = {}
......@@ -113,22 +167,5 @@ class Diagram():
attack = self.attacks[id]
return attack
def getERDynamic(self, threatID) -> ERDynamic:
"""_summary_
The get ER dynamic is used to retrieve the ER dynamic component associated with a threat
This is done through the metric matrix and threat ID
1. Threat ID is used to find a row in the metric matrix which contains the threat ID,
Args:
threatID (_type_): _description_
Returns:
ERDynamic: _description_
"""
pass
def getBowtieDynamic(self, associatedId) -> BowtieDynamic:
pass
def getMetric(self) -> Metric:
def getIndicator(self, indicaorID) -> ERDynamic:
pass
\ No newline at end of file
......@@ -13,110 +13,117 @@ def parseDiagramFile(csvFile) -> component.Diagram:
diagram = component.Diagram() # Defines the diagram object
# List containing all threats and their descriptions
threats = diagram.threats
consequences = diagram.consequences
dynamics = diagram.dynamics
metrics = diagram.metrics
diagram = matrix.parseTable(df, diagram) #Parse the table and finds all indicators
diagram = parseArchitectureDiagram(df, diagram) #Parse the architecture diagram and finds all ER components along with indicators and reasoning
threats = parseThreats(df, threats)
consequences = parseConsequences(df, consequences)
diagram = parseThreats(df, diagram) #
diagram = parseConsequences(df, diagram)
diagram = parseAttacks(df, diagram)
metrics, dynamics = parseDynamic(df, metrics, dynamics)
metricsMatrix, metrics = matrix.parseTable(df, metrics, diagram) #Parse the table
return diagram
def parseThreats(df, threatDict):
# Function will parse the threats and add them to the dictionary
def parseThreats(df,diagram: component.Diagram):
for i in range(len(df)):
if df[const.ThreatValue][i] == const.Threat: #Creates threat object
if df[const.ThreatComponent][i] == const.Threat: #If the row in the dataframe is a threat
threat = component.Threat(
df[const.Id][i], # Component ID
df[const.ThreatIDValue][i], # ID from ER
df[const.Id][i], # LucidChart ID
df[const.ThreatSourceValue][i],
df[const.ThreatDescriptionValue][i],
df[const.LikelihoodValue][i],
df[const.VulnerabilityValue][i]
df[const.ThreatName][i], # Name of the threat
df[const.ThreatDescription][i],
df[const.ThreatSource][i],
df[const.Likelihood][i],
)
threatDict[df.Id[i]] = threat
diagram.threats[df[const.ThreatIDValue][i]] = threat
return threatDict
threat.findIndicatorsThreat(df, diagram, threat) # Find the indicators associated with the threat
return diagram
#Parses consequences and adds it to dictionary
def parseConsequences(df, consequenceDict):
def parseConsequences(df, diagram: component.Diagram):
for i in range(len(df)):
if df[const.ConsequenceValue][i] == const.Consequence:
if df[const.ConsequenceComponent][i] == const.Consequence:
consequence = component.Consequence(
df[const.ConsequenceIDValue][i],
df[const.Id][i],
df[const.ConsequenceDescriptionValue][i],
df[const.ConsequenceScoreValue][i],
df[const.AffectedComponentValue][i]
df[const.ConsequenceID][i],
df[const.ConsequenceName][i],
df[const.ConsequenceDescription][i],
df[const.ConsequenceScore][i],
)
consequenceDict[df.Id[i]] = consequence
diagram.consequences[df[const.ConsequenceID][i]] = consequence
return consequenceDict
return diagram
def parseAttacks(df, attackDict):
def parseAttacks(df, diagram: component.Diagram):
for i in range(len(df)):
if df[const.AttackValue][i] == const.Attack:
if df[const.AttackComponent][i] == const.Attack:
attack = component.Attack(
df[const.Id][i],
df[const.AttackedComponentValue][i],
df[const.AttackDescriptionValue][i],
df[const.AttackType][i],
df[const.AttackDescription][i],
)
attackDict[df.Id[i]] = attack
diagram.attacks[df.Id[i]] = attack #! Note that the ID is the component ID from LucidChart, not my ID
return attackDict
return diagram
#Parses metrics components and adds it to list
def parseDynamic(df, metricDict, dynamicsDict):
for i in range(len(df)): # Iterates through the dataframe
if df[const.textArea1][i] == const.Dynamics: # If the component is a dynamic component
if df[const.textArea3][i] == const.ThreatDynamic: # If the dynamic component is a threat
threatDynamic = dynamic.BowtieDynamic(
df[const.Id][i], # Component ID LucidChart
df[const.textArea3][i] # Name of dynamic
)
def parseArchitectureDiagram(df, diagram: component.Diagram):
for i in range(len(df)):
# Iterates through the dataframe
if df[const.textArea1][i] == const.relationship: # If the component is a dynamic component
if df[const.typeField][i] == "ER": # * If the component is an ER component # Find the line where the component is the source
diagram = parseRelationshipComponentER(df, diagram, i)
return diagram
threatDynamic.associateBowtie(df, threatDynamic.type, metricDict) # Associate the dynamic with the correct components
dynamicsDict[df.Id[i]] = threatDynamic
def parseRelationshipComponentER(df: pd.DataFrame, diagram: component.Diagram, row) -> None:
elif df[const.textArea3][i] == const.ConsequenceDynamic:
consequenceDynamic = dynamic.BowtieDynamic(
df[const.Id][i], # Component ID LucidChart
df[const.textArea3][i] # Name of dynamic
)
relationshipComponent = df.loc[row] # Define the relationship component
consequenceDynamic.associateBowtie(df, consequenceDynamic.type, metricDict)
dynamicsDict[df.Id[i]] = consequenceDynamic
print("Parsing ER component: ", relationshipComponent[const.erName])
print(relationshipComponent[const.textArea5])
elif df[const.textArea3][i] == const.AttackDynamic:
attackDynamic = dynamic.BowtieDynamic(
df[const.Id][i], # Component ID LucidChart
df[const.textArea3][i] # Name of dynamic
)
indicatorList = {} # Define a list over relevant indicators to individual relationship component
erComponents = {} # Define a list over ER components
attackDynamic.associateBowtie(df,attackDynamic.type, metricDict) # Associate the dynamic with the correct components
dynamicsDict[df.Id[i]] = attackDynamic
#* Iterate over the Relationship component to find all the indicators, and the reasoning for each
for i in range(len(6 ,relationshipComponent.columns, 2)):
indicatorID = relationshipComponent[f"Text Area {i}"].item()
indicatorReason = relationshipComponent[f"Text Area {i+1}"].item()
indicatorList[indicatorID] = indicatorReason
elif df[const.textArea3][i] == const.ERDynamic:
erDynamic = dynamic.ERDynamic(
df[const.Id][i], # Component ID LucidChart
df[const.textArea3][i], # Component type
df[const.textArea5][i] # Description
)
# * Find all the lines associated with the component
linesFrom = df.loc[df[const.From] == relationshipComponent[const.Id].item()] # Find the component associated with the line
linesTo = df.loc[df[const.To] == relationshipComponent[const.Id].item()] # Find the component associated with the line
linesFrom.append(linesTo) # Merge the two dataframes
erDynamic.associatER(df, metricDict)
dynamicsDict[df.Id[i]] = erDynamic
return metricDict, dynamicsDict
# * Iterate through both lines, and find the ER components related to the relationship component
for i in range(len(linesFrom)):
# Defines values
erName = linesFrom[const.Name][i].item()
erID = linesFrom[const.erID][i].item()
erDescription = linesFrom[const.erDescription][i].item()
erType = linesFrom[const.erType][i].item()
# Create ER component
erComponent = component.ERComponent(erName,erID, erDescription, erType)
# Add indicators to the ER component
erComponent.linkedIndicators.update(indicatorList)
erComponents[erComponent.id]= erComponent
for i in range (indicatorList):
indicatorID = i.key()
# We need to update the indicators with the ER components and the reasoning for the indicator annotation
diagram.indicators[indicatorID].erAssocaites.update(erComponents)
return diagram
def extractMetrics(df, index, startRange):
......
import ERFormatConstants as const
import logging as log
import components as component
class DynamicComponent:
"""_summary_
Dynamic component, parent class of bowtie dynamic and ER dynamic
It contains a metric list which contains the ID of the metrics associated with the dynamic component
"""
def __init__(self, componentID) -> None:
self.componentID = componentID
self.metrics = [] # List of metrics for the dynamic
#String only returns necessary info
def __str__(self) -> str:
return f"Dynamic Component: {self.componentID}, {len(self.metrics)}"
#Metric class, will be used for all dynamics
class Metric:
class Indicator:
"""
The metric class is associated with all the dynamics components AND the matrix
The initial creation of metrics is done through the dynamic units, there the init function is used to create the metrics,
and fill them with the correct associated components (erID, bowtieID)
When the object is then accessed again for the matrix parse the metrics are filled with the correct values
"""
def __init__(self, ID, name) -> None:
def __init__(self, ID, name, value, date, frequency, guide, scale, interpretation) -> None:
"""
Metrics are created when parsing dynamics and are there filled with args
When the matrix is parsed the metrics are filled with the correct values
......@@ -33,105 +20,26 @@ class Metric:
name (str): Unique name for the metric
"""
#Metrics found in the dynamics tables
self.erID = [] # ID of the metric used to locate in dynamics matrics
self.bowtieID = [] # ID of the metric used to locate in dynamics matrics
self.ID = ID # ID of the metric used to locate in dynamics matrics
self.name = name # Name of the metric
self.value = None # Value of the metric
self.date = None # Date of the metric
self.frequency = None # Frequency of the metric
self.measureGuide = None # Measure guide of the metric
self.erAssociates: dict[int, component.ERComponent] = {} # ID of the metric used to locate in dynamics matrics
self.threatAssoicates: dict[str, component.Threat] = {} # ID of the metric used to locate in dynamics matrics
self.consequenceAssociates: dict[str, component.Consequence] = {} # ID of the metric used to locate in dynamics matrics
self.attackAssociates: dict[int, component.Attack] = {} # ID of the metric used to locate in dynamics matrics
def __str__(self) -> str:
return f"Metric: {self.name}, Value: {self.value} Last update: {self.date}"
class BowtieDynamic(DynamicComponent):
"""
The bowtie dynamic inherits the dynamic component and adds associated threats or consequences and attacks
Contains its own associate function and its own linkMetric function to link metrics
Args:
DynamicComponent (class): Parent class
"""
def __init__(self, componentID, type) -> None:
super().__init__(componentID)
self.type = type # Type of dynamicM
self.associatedThreat = None # Threat associated with the dynamic
self.associatedAttack = None # Attack associated with the dynamic
self.associatedConsequence = None # Consequence associated with the dynamic
self.indicatorID = ID # ID of the metric used to locate in dynamics matrics
self.indicatorName = name # Name of the metric
self.value = value # Value of the metric
self.date = date # Date of the metric
self.frequency = frequency # Frequency of the metric
self.measureGuide =guide # Measure guide of the metric
self.scale = scale # Scale of the metric
self.interpretation = interpretation # Interpretation of the metric
def __str__(self) -> str:
"""
This string function is used to print the dynamic component
Based on the different types of dynamics the string function will return different information
Returns:
str: Print string
"""
if self.type == const.ThreatDynamic:
return super().__str__() + f"Associated threat ID: {self.associatedThreat[const.Id].item()} Associated attack ID {(self.associatedAttack[const.Id].item())}"
elif self.type == const.ConsequenceDynamic:
return super().__str__() + f"Associated consequence ID: {self.associatedConsequence[const.Id].item()} Assocaited attack ID:{(self.associatedAttack[const.Id].item())}"
elif self.type == const.AttackDynamic:
return super().__str__() + f"Associated attack ID: {self.associatedAttack[const.Id].item()}"
def associateBowtie(self, df, componentType: str, metricsDict: dict):
"""
Handles the associated dynamics for the bowtie model as they are different from the ER model
ER model has one type of dynamic component with metrics however, the bowtie model has three dynamic types and needs more parsing
than the ER model du to the modeling annotation.
Abstracting this saves time
"""
componentId = self.componentID # Define the ID of the component
lines = df.loc[df['Name']=='Line'] # Find all lines in the diagram
lineSource = lines.loc[lines[const.From] == componentId] # Find the line where the component is the source
lineDestination = lines.loc[lines[const.To] == componentId] # ! The component SHOULD be source however, to error handle we need to check for destination as well
if lineSource.isnull().values.any() == True:
lineOne = lineSource
else:
lineOne = lineDestination
if lineOne[const.From].item() == componentId: # If the component is the source
lineTwo = df.loc[df[const.Id] == lineOne[const.To].item()] # Find the component associated with the line
else:
lineTwo = df.loc[df[const.Id] == lineOne[const.From].item()] # Find the component associated with the line
# ! Edge case, if the second line is in fact not a second line it is an attack meaning that the dynamic component is an attack
if componentType == const.Attack: # If the associated component is an attack
self.associatedAttack = lineTwo # ! The "line" is an attack, not a line, we add it to the associated attack field and move on
self.linkMetric(df, metricsDict)
return # The attack is the only component associated with the dynamic
sourceComponent = df.loc[df[const.Id]==lineTwo[const.From].item()] # Defines the source of the line connecting threats/consequences and attacks
destinationComponent = df.loc[df[const.Id]==lineTwo[const.To].item()] # Defines the destination of the line connecting threats/consequences and attacks
if componentType == const.ThreatDynamic: # * If the component is a threat
if sourceComponent[const.textArea1].item() == const.Threat: # Checks if source or destination is the threat
self.associatedThreat = sourceComponent
self.associatedAttack = destinationComponent
else:
self.associatedThreat = destinationComponent
self.associatedAttack = sourceComponent
elif componentType == const.ConsequenceDynamic: # * If the component is a consequence
return f"Metric: {self.name}, Value: {self.value} Last update: {self.date}"
if sourceComponent[const.textArea1].item() == const.Consequence:
self.associatedConsequence = sourceComponent
self.associatedAttack = destinationComponent
else:
self.associatedConsequence = destinationComponent
self.associatedAttack = sourceComponent
# ! After parsing the different components related to the dynamics component,
# ! the metrics in the component are added to the global metric list and local metric list
self.linkMetric(df, metricsDict)
def linkMetric(self, df, metricsDict: dict):
"""
......
import pandas as pd
import ERFormatConstants as const
import components as component
import dynamics as dynamic
"""_summary_
Parses csv table and returns a dataframe
This is quite finicky as lucidchart has a very weird way of structuring tables as it is based on when different cells were created
This is quite a big EDGE case and will only work for the specific table shape that I have created
"""
def parseTable(df, metricsDict, diagram):
def parseTable(df, diagram: component.Diagram) -> component.Diagram:
cols = [const.Metric_ID, const.Metric_Name, const.Value, const.Measure_date, const.Frequency ,const.Measurement_guide]
cols = [const.Metric_ID, const.Metric_Name, const.Value, const.Measure_date, const.Frequency ,const.Measurement_guide, const.Scale, const.Interpretation]
table = df.loc[df[const.Name]=='Table'] # Defines the table
matrics = pd.DataFrame(columns=cols)
stopLength = 226
# ! Prior to parsing the entire table, the first two rows need to be specifically created,
# ! This is due to the way the table is structured in LucidChart, the first 6 cells comes first, then the set of standard indicators are next
# ! Next is the top of the table again with the two rows and 5 columns before new indicators are parsed FCk this shit
# * Start table | 1 | 2 | 3 | 170 | 171 | 172 | 173 | 174 |
# * Row 1 | 4 | 5 | 6 | 175 | 176 | 177 | 178 | 179 |
# * Row 2 | 7 | 8 | 9 | 180 | 181 | 182 | 183 | 184 |
# * Row . . . . . . . . .. . . . . . . . . . . .. . . . .
# * Row 23 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 |
# * Row 24 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 191 |
# * Etc
# ! Parsing the first row of the table which has a fucked structure because lucidchart is garbage : )
MetricID1 = table[const.textArea4].item() # Define the Metric ID
MetricName1 = table[const.textArea5].item() # Define the Metric Name
Value1 = table[const.textArea6].item() # Define the Value
MeasureDate1 = table[f"Text Area 229"].item() # Define the Measure Date
Frequency1 = table[f"Text Area 230"].item() # Define the Frequency
Guide1 = table[f"Text Area 231"].item() # Define the Measurement guide
MetricID1 = int(MetricID1)
matrics.loc[0] = MetricID1, MetricName1, Value1, MeasureDate1, Frequency1, Guide1 # Fill inn dataframe
startStandardIndicators = 21-4 # Column 21-4
stopStandardIndicators = 180-4 # First loop needs to stop at column 180-4, 4 columns are removed in diagramParser.py
interval = len(cols)
startSpecialIndicators = 196-4 # Text area 185
stopSpecializedIndicators = len(table.columns)-2
# TODO Error handle/Communicate to end user here, "You seem to have a metric in your table which is not used etc"
fillMetricinfo(MetricID1, MetricName1, Value1, MeasureDate1, Frequency1, Guide1, metricsDict, diagram)
MetricID2 = table[const.textArea7].item() # Define the Metric ID
MetricName2 = table[const.textArea8].item() # Define the Metric Name
indicatorID1 = table[const.textArea4].item() # Define the Metric ID
indicatorName1 = table[const.textArea5].item() # Define the Metric Name
Value1 = table[const.textArea6].item() # Define the Value
MeasureDate1 = table[f"Text Area 175"].item() # Define the Measure Date
Frequency1 = table[f"Text Area 176"].item() # Define the Frequency
Guide1 = table[f"Text Area 177"].item() # Define the Measurement guide
Scale1 = table[f"Text Area 178"].item()
Interpretation1 = table[f"Text Area 179"].item()
indicatorID1 = int(indicatorID1) # Convert indicator to int
#* Create indicator object
indicator1 = dynamic.Indicator(
indicatorID1,
indicatorName1,
Value1,
MeasureDate1,
Frequency1,
Guide1,
Scale1,
Interpretation1
)
diagram.indicators[indicatorID1] = indicator1 # Insert indicator into diagram dictionary
IndicatorID2 = table[const.textArea7].item() # Define the Metric ID
IndicatorName2 = table[const.textArea8].item() # Define the Metric Name
Value2 = table[const.textArea9].item() # Define the Value
MeasureDate2 = table[f"Text Area 232"].item() # Define the Measure Date
Frequency2 = table[f"Text Area 233"].item() # Define the Frequency
Guide2 = table[f"Text Area 234"].item() # Define the Measurement guide
MetricID2 = int(MetricID2)
matrics.loc[1] = MetricID2, MetricName2, Value2, MeasureDate2, Frequency2, Guide2
metric = metricsDict[MetricID2] # Access the appropriate metric in the dictionary
fillMetricinfo(MetricID2, MetricName2, Value2, MeasureDate2, Frequency2, Guide2, metricsDict, diagram)
# TODO Error handle/Communicate to end user here, "You seem to have a metric in your table which is not used etc"
if metric == None:
handleMissingMetric()
matricsIndex = 2
# Parsing from the third row and onwards is standard format
for i in range(10, stopLength, 6):
if i >= stopLength:
MeasureDate2 = table[f"Text Area 180"].item() # Define the Measure Date
Frequency2 = table[f"Text Area 181"].item() # Define the Frequency
Guide2 = table[f"Text Area 182"].item() # Define the Measurement guide
Scale2 = table[f"Text Area 183"].item()
Interpretation2 = table[f"Text Area 184"].item()
IndicatorID2 = int(IndicatorID2)
indicator2 = dynamic.Indicator(
IndicatorID2,
IndicatorName2,
Value2,
MeasureDate2,
Frequency2,
Guide2,
Scale2,
Interpretation2
)
diagram.indicators[IndicatorID2] = indicator2 # Insert indicator into diagram dictionary
# ! Parsing from the third row and onwards is until row 23, the last row is the standard indicators
for i in range(startStandardIndicators, stopStandardIndicators, interval):
if i >= stopStandardIndicators:
break
MetricID = table["Text Area "+str(i)].item() # Define the Metric ID
MetricName = table["Text Area "+str(i+1)].item() # Define the Metric Name
IndicatorID = table["Text Area "+str(i)].item() # Define the Metric ID
IndicatorName = table["Text Area "+str(i+1)].item() # Define the Metric Name
Value = table["Text Area "+str(i+2)].item() # Define the Value
MeasureDate = table["Text Area "+str(i+3)].item() # Define the Measure Date
Frequency = table["Text Area "+str(i+4)].item() # Define the Frequency
Guide = table["Text Area "+str(i+5)].item() # Define the Measurement guide
matrics.loc[matricsIndex] = MetricID, MetricName, Value, MeasureDate, Frequency, Guide
print("Matrise:")
print(matrics)
fillMetricinfo(MetricID, MetricName, Value, MeasureDate, Frequency, Guide,metricsDict, diagram)
matricsIndex += 1
return matrics, metricsDict
def handleMissingMetric():
"""Function will communicate to end user that a metric is in the matrics but is not used in the diagram
This will provide clarity to the end user and inform them of unlinked metrics
"""
pass
def fillMetricinfo(
MetricID1: str,
MetricName1: str,
Value1: str,
MeasureDate1: str,
Frequency1: str,
Guide1: str, metricsDict: dict, diagram)-> dict:
"""Function will fill in the metric information in the dataframe into the metric object and return the updated dictionary
"""
MetricID1 = int(MetricID1)
if MetricID1 in metricsDict:
metric = metricsDict[MetricID1] # Access the appropriate metric in the dictionary
else:
print("Metric is not used in the diagram!!!")
return
metric.name = MetricName1 # Should be string
metric.value = Value1 # Should be string
metric.date = MeasureDate1 # TODO Parse this and change it to a date object maybe? Not sure if its necessary...
metric.frequency = Frequency1 # Should be string
metric.guide = Guide1 # Should be string
metricsDict[MetricID1] = metric
indicator = dynamic.Indicator(
IndicatorID,
IndicatorName,
Value,
MeasureDate,
Frequency,
Guide
)
diagram.indicators[IndicatorID] = indicator # Insert indicator into diagram dictionary
# ! Parsing will then continue from the last row, row 24, and onwards with usual text areas for specialized indicators
for i in range(startSpecialIndicators, stopSpecializedIndicators, interval):
if i >= stopSpecializedIndicators:
break
return metricsDict
IndicatorID = table["Text Area "+str(i)].item() # Define the Metric ID
IndicatorName = table["Text Area "+str(i+1)].item() # Define the Metric Name
Value = table["Text Area "+str(i+2)].item() # Define the Value
MeasureDate = table["Text Area "+str(i+3)].item() # Define the Measure Date
Frequency = table["Text Area "+str(i+4)].item() # Define the Frequency
Guide = table["Text Area "+str(i+5)].item() # Define the Measurement guide
def findComponentIDBowtie(id, diagram):
indicator = dynamic.Indicator(
IndicatorID,
IndicatorName,
Value,
MeasureDate,
Frequency,
Guide
)
diagram.indicators[IndicatorID] = indicator
for threat in diagram.threats.values():
if threat.id == str(id):
return threat.componentID
for consequence in diagram.consequences.values():
if consequence.id == str(id):
return consequence.componentID
\ No newline at end of file
return diagram
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment