Skip to content

Conversation

santoromarco74
Copy link
Owner

@santoromarco74 santoromarco74 commented Oct 20, 2025

Summary by Sourcery

Upgrade the GIST assessment web application to a full-featured interactive tool: expand the questionnaire to 36 calibrated questions, refactor the scoring engine for richer outputs (maturity levels, recommendations, benchmarks, ROI), and redesign the Flask front-end into modular templates with Tailwind CSS and Chart.js for a modern user experience.

New Features:

  • Expand questionnaire to 36 GIST assessment questions with calibrated weights, sector benchmarks, and Italian labels
  • Refactor GISTCalculator to modular methods for component scoring, non-linear overall score, maturity levels, prioritized recommendations, ROI estimates, and sector benchmarks
  • Revamp Flask app with new REST API endpoint, separate assessment and results routes, PDF generation stub, and improved error handling
  • Implement interactive front-end templates (base, assessment, results, about) using Tailwind CSS and Chart.js for real-time progress, radar/bar charts, and dynamic recommendations

Enhancements:

  • Overhaul UI/UX with Tailwind CSS, Hero and features sections, sticky navigation, and responsive design
  • Reorganize questionnaire and calculator logic into dedicated modules with clear docstrings and private methods
  • Streamline requirements, update to Flask 3.0.0, and add ReportLab for future PDF report generation

Build:

  • Update requirements.txt to remove duplicates and pin Flask 3.0.0 and reportlab

Documentation:

  • Add comprehensive README with quick start, deployment instructions, architecture overview, and roadmap

@sourcery-ai
Copy link

sourcery-ai bot commented Oct 20, 2025

Reviewer's Guide

This PR upgrades the GIST assessment to Opzione A by fully expanding the questionnaire schema, refactoring the calculation engine into modular methods, overhauling the Flask app with a JSON API and structured routes, revamping frontend templates under a Tailwind-based Jinja layout, and updating dependencies and project documentation.

Entity relationship diagram for questionnaire and component weights/benchmarks

erDiagram
    COMPONENT_WEIGHTS {
        physical float
        architectural float
        security float
        compliance float
    }
    SECTOR_BENCHMARKS {
        physical avg float
        physical target float
        architectural avg float
        architectural target float
        security avg float
        security target float
        compliance avg float
        compliance target float
    }
    COMPONENT_LABELS {
        physical string
        architectural string
        security string
        compliance string
    }
    QUESTIONNAIRE {
        physical list
        architectural list
        security list
        compliance list
    }
    QUESTIONNAIRE ||--o{ COMPONENT_WEIGHTS : "weights for"
    QUESTIONNAIRE ||--o{ SECTOR_BENCHMARKS : "benchmarks for"
    QUESTIONNAIRE ||--o{ COMPONENT_LABELS : "labels for"
Loading

Class diagram for the expanded GISTCalculator and questionnaire data model

classDiagram
    class GISTCalculator {
        - organization_name: str
        - weights: dict
        - gamma: float
        - benchmarks: dict
        + __init__(organization_name)
        + calculate_score(questionnaire_answers)
        - _calculate_component_scores(answers)
        - _calculate_gist_formula(component_scores)
        - _get_maturity_level(gist_score)
        - _generate_recommendations(component_scores)
        - _calculate_priority(gap, component)
        - _get_action_plan(component, current, target)
        - _estimate_effort(gap)
        - _estimate_roi(component, gap)
        - _calculate_benchmarks(component_scores)
        - _score_to_percentile(vs_sector)
    }
    class QUESTIONNAIRE {
        physical: list[Question]
        architectural: list[Question]
        security: list[Question]
        compliance: list[Question]
    }
    class Question {
        id: str
        question: str
        options: list[Option]
    }
    class Option {
        value: int
        text: str
    }
    GISTCalculator --> "1" QUESTIONNAIRE : uses
    QUESTIONNAIRE "1" --> "*" Question
    Question "1" --> "*" Option
Loading

File-Level Changes

Change Details Files
Expanded questionnaire schema with full set of questions and metadata
  • Renamed question IDs and refined question texts
  • Added new questions across physical, architectural, security, and compliance categories
  • Introduced component weights, sector benchmarks, and component labels constants
questionnaire.py
Refactored GIST calculation logic into modular components
  • Separated component scoring, GIST formula, maturity level, recommendations, and benchmarks into private methods
  • Integrated timestamp and organization metadata into result payload
  • Adjusted returned structure for richer frontend integration
gist_calculator.py
Overhauled Flask application to support interactive API and new routes
  • Replaced legacy form route with a /api/calculate JSON endpoint
  • Added /assessment, /results, /about routes and error handlers
  • Integrated the GISTCalculator class and structured request/response handling
app.py
Reworked frontend templates using Tailwind CSS and modular Jinja blocks
  • Created a base layout and split pages into index, assessment, results, and about templates
  • Implemented responsive components with progress tracking, charts, and interactive features
  • Integrated Chart.js and sessionStorage for dynamic result rendering
templates/base.html
templates/index.html
templates/assessment.html
templates/results.html
templates/about.html
Updated project dependencies and documentation
  • Upgraded Flask to version 3 and added reportlab for future PDF support
  • Removed duplicate entries and cleaned up requirements.txt
  • Added a comprehensive README with setup, deployment, and roadmap
requirements.txt
README.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

Blocking issues:

  • Detected Flask app with debug=True. Do not deploy to production with this flag enabled as it will leak sensitive information. Instead, consider using Flask configuration variables or setting 'debug' using system environment variables. (link)

General comments:

  • Move the hard‐coded app.secret_key in app.py into an environment variable or config file to avoid exposing secrets in source control.
  • The GISTCalculator class has grown very large—consider splitting calculation, recommendation logic, and benchmarking into separate modules or helper classes to improve readability and testability.
  • questionnaire.py is now a massive static structure—think about extracting the question definitions into a JSON/YAML file or separate module to make updates and maintenance easier.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Move the hard‐coded app.secret_key in app.py into an environment variable or config file to avoid exposing secrets in source control.
- The GISTCalculator class has grown very large—consider splitting calculation, recommendation logic, and benchmarking into separate modules or helper classes to improve readability and testability.
- questionnaire.py is now a massive static structure—think about extracting the question definitions into a JSON/YAML file or separate module to make updates and maintenance easier.

## Individual Comments

### Comment 1
<location> `gist_calculator.py:64-65` </location>
<code_context>
+        """Calcola score medio 0-100 per ogni componente."""
+        component_scores = {}
+        
+        for component, questions in answers.items():
+            if questions:
+                # Media dei valori delle risposte
+                values = list(questions.values())
</code_context>

<issue_to_address>
**issue:** Consider handling missing or malformed question values more robustly.

The code does not validate question values, which may cause errors or incorrect results if values are missing or non-numeric. Add checks or defaults to ensure reliable score calculations.
</issue_to_address>

### Comment 2
<location> `gist_calculator.py:79-84` </location>
<code_context>
+        """
+        gist_score = 0
+        
+        for component, score in component_scores.items():
+            if component in self.weights:
+                # Formula con esponente per rendimenti decrescenti
+                weighted_score = self.weights[component] * (score ** self.gamma)
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Skip components not present in weights to avoid silent errors.

Consider adding a log message or raising an error when a component is missing from self.weights to prevent silent miscalculations due to typos or unexpected input.

```suggestion
        import logging
        logger = logging.getLogger(__name__)

        gist_score = 0

        for component, score in component_scores.items():
            if component in self.weights:
                # Formula con esponente per rendimenti decrescenti
                weighted_score = self.weights[component] * (score ** self.gamma)
            else:
                logger.warning(f"Component '{component}' not found in weights. Skipping this component.")
                continue
```
</issue_to_address>

### Comment 3
<location> `gist_calculator.py:227-229` </location>
<code_context>
         }

+        # ROI scala con il gap da colmare
+        roi = base_roi[component] * (gap / 30)
+        return f"{int(roi)}% a 3 anni"
+    
</code_context>

<issue_to_address>
**suggestion:** Guard against negative or zero gap values in ROI calculation.

If gap is zero or negative, ROI may be zero or negative, which is likely not meaningful. Consider ensuring ROI is non-negative or handling non-positive gap values explicitly.

```suggestion
        # ROI scala con il gap da colmare
        if gap <= 0:
            return "Nessun ROI calcolabile: gap nullo o negativo"
        roi = base_roi[component] * (gap / 30)
        roi = max(0, roi)
        return f"{int(roi)}% a 3 anni"
```
</issue_to_address>

### Comment 4
<location> `app.py:49-55` </location>
<code_context>
+        
+        # Calcola GIST Score
+        calculator = GISTCalculator(company_name)
+        results = calculator.calculate_score(answers)

-        score_calcolato = calcola_gist_score(punteggi)
</code_context>

<issue_to_address>
**suggestion:** Consider validating the structure of answers before calculation.

Malformed or incomplete answer data could cause errors or incorrect results. Adding validation will help ensure reliable calculations.

```suggestion
        # Validazione base
        if not answers:
            return jsonify({'error': 'Nessuna risposta fornita'}), 400

        def validate_answers_structure(answers):
            # Esempio di validazione: answers deve essere un dict con almeno una chiave e valore numerico
            if not isinstance(answers, dict):
                return False
            if not answers:
                return False
            for key, value in answers.items():
                if not isinstance(key, str):
                    return False
                if not isinstance(value, (int, float)):
                    return False
            return True

        if not validate_answers_structure(answers):
            return jsonify({'error': 'Struttura delle risposte non valida'}), 400

        # Calcola GIST Score
        calculator = GISTCalculator(company_name)
        results = calculator.calculate_score(answers)
```
</issue_to_address>

### Comment 5
<location> `templates/results.html:174` </location>
<code_context>
+{% block extra_scripts %}
+<script>
+// Load results from sessionStorage
+const results = JSON.parse(sessionStorage.getItem('gist_results'));
+
+if (!results) {
</code_context>

<issue_to_address>
**suggestion:** Consider fallback for missing or corrupted sessionStorage data.

Instead of redirecting immediately, show an error message or offer a recovery option to improve user experience.
</issue_to_address>

### Comment 6
<location> `app.py:101` </location>
<code_context>
    app.run(debug=True, host='0.0.0.0', port=5000)
</code_context>

<issue_to_address>
**security (python.flask.security.audit.debug-enabled):** Detected Flask app with debug=True. Do not deploy to production with this flag enabled as it will leak sensitive information. Instead, consider using Flask configuration variables or setting 'debug' using system environment variables.

*Source: opengrep*
</issue_to_address>

### Comment 7
<location> `gist_calculator.py:79-87` </location>
<code_context>
    def _calculate_gist_formula(self, component_scores):
        """
        Applica formula GIST originale: Σ(w_k * S_k^γ)
        """
        gist_score = 0

        for component, score in component_scores.items():
            if component in self.weights:
                # Formula con esponente per rendimenti decrescenti
                weighted_score = self.weights[component] * (score ** self.gamma)
                gist_score += weighted_score

        return gist_score

</code_context>

<issue_to_address>
**suggestion (code-quality):** We've found these issues:

- Convert for loop into call to sum() ([`sum-comprehension`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/sum-comprehension/))
- Inline variable that is only used once ([`inline-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-variable/))
- Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-immediately-returned-variable/))

```suggestion
        return sum(
            self.weights[component] * (score**self.gamma)
            for component, score in component_scores.items()
            if component in self.weights
        )
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +64 to +65
for component, questions in answers.items():
if questions:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Consider handling missing or malformed question values more robustly.

The code does not validate question values, which may cause errors or incorrect results if values are missing or non-numeric. Add checks or defaults to ensure reliable score calculations.

Comment on lines +79 to +84
gist_score = 0

for component, score in component_scores.items():
if component in self.weights:
# Formula con esponente per rendimenti decrescenti
weighted_score = self.weights[component] * (score ** self.gamma)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Skip components not present in weights to avoid silent errors.

Consider adding a log message or raising an error when a component is missing from self.weights to prevent silent miscalculations due to typos or unexpected input.

Suggested change
gist_score = 0
for component, score in component_scores.items():
if component in self.weights:
# Formula con esponente per rendimenti decrescenti
weighted_score = self.weights[component] * (score ** self.gamma)
import logging
logger = logging.getLogger(__name__)
gist_score = 0
for component, score in component_scores.items():
if component in self.weights:
# Formula con esponente per rendimenti decrescenti
weighted_score = self.weights[component] * (score ** self.gamma)
else:
logger.warning(f"Component '{component}' not found in weights. Skipping this component.")
continue

Comment on lines +227 to +229
# ROI scala con il gap da colmare
roi = base_roi[component] * (gap / 30)
return f"{int(roi)}% a 3 anni"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Guard against negative or zero gap values in ROI calculation.

If gap is zero or negative, ROI may be zero or negative, which is likely not meaningful. Consider ensuring ROI is non-negative or handling non-positive gap values explicitly.

Suggested change
# ROI scala con il gap da colmare
roi = base_roi[component] * (gap / 30)
return f"{int(roi)}% a 3 anni"
# ROI scala con il gap da colmare
if gap <= 0:
return "Nessun ROI calcolabile: gap nullo o negativo"
roi = base_roi[component] * (gap / 30)
roi = max(0, roi)
return f"{int(roi)}% a 3 anni"

Comment on lines +49 to +55
# Validazione base
if not answers:
return jsonify({'error': 'Nessuna risposta fornita'}), 400

# Calcola GIST Score
calculator = GISTCalculator(company_name)
results = calculator.calculate_score(answers)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider validating the structure of answers before calculation.

Malformed or incomplete answer data could cause errors or incorrect results. Adding validation will help ensure reliable calculations.

Suggested change
# Validazione base
if not answers:
return jsonify({'error': 'Nessuna risposta fornita'}), 400
# Calcola GIST Score
calculator = GISTCalculator(company_name)
results = calculator.calculate_score(answers)
# Validazione base
if not answers:
return jsonify({'error': 'Nessuna risposta fornita'}), 400
def validate_answers_structure(answers):
# Esempio di validazione: answers deve essere un dict con almeno una chiave e valore numerico
if not isinstance(answers, dict):
return False
if not answers:
return False
for key, value in answers.items():
if not isinstance(key, str):
return False
if not isinstance(value, (int, float)):
return False
return True
if not validate_answers_structure(answers):
return jsonify({'error': 'Struttura delle risposte non valida'}), 400
# Calcola GIST Score
calculator = GISTCalculator(company_name)
results = calculator.calculate_score(answers)

{% block extra_scripts %}
<script>
// Load results from sessionStorage
const results = JSON.parse(sessionStorage.getItem('gist_results'));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider fallback for missing or corrupted sessionStorage data.

Instead of redirecting immediately, show an error message or offer a recovery option to improve user experience.

if __name__ == '__main__':
app.run(debug=True)
# Development mode
app.run(debug=True, host='0.0.0.0', port=5000)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.flask.security.audit.debug-enabled): Detected Flask app with debug=True. Do not deploy to production with this flag enabled as it will leak sensitive information. Instead, consider using Flask configuration variables or setting 'debug' using system environment variables.

Source: opengrep

Comment on lines +79 to +87
gist_score = 0

for component, score in component_scores.items():
if component in self.weights:
# Formula con esponente per rendimenti decrescenti
weighted_score = self.weights[component] * (score ** self.gamma)
gist_score += weighted_score

return gist_score
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): We've found these issues:

Suggested change
gist_score = 0
for component, score in component_scores.items():
if component in self.weights:
# Formula con esponente per rendimenti decrescenti
weighted_score = self.weights[component] * (score ** self.gamma)
gist_score += weighted_score
return gist_score
return sum(
self.weights[component] * (score**self.gamma)
for component, score in component_scores.items()
if component in self.weights
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant