{"id":26879557,"url":"https://github.com/mrezkys/swiftprofilematching","last_synced_at":"2025-10-11T12:21:51.039Z","repository":{"id":285392645,"uuid":"947328279","full_name":"mrezkys/SwiftProfileMatching","owner":"mrezkys","description":"A Swift Package for implementing Profile Matching Method - a popular technique used in Decision Support Systems (DSS).","archived":false,"fork":false,"pushed_at":"2025-03-18T08:28:01.000Z","size":55,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-31T14:04:02.911Z","etag":null,"topics":["dss","profile-matching","swift","swiftpackage","swiftpackagemanager"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrezkys.png","metadata":{},"created_at":"2025-03-12T14:13:35.000Z","updated_at":"2025-03-22T18:43:10.000Z","dependencies_parsed_at":null,"dependency_job_id":"704c2b3e-d3d4-4936-be7e-199972277afa","html_url":"https://github.com/mrezkys/SwiftProfileMatching","commit_stats":null,"previous_names":["mrezkys/swiftprofilematching"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/mrezkys/SwiftProfileMatching","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrezkys%2FSwiftProfileMatching","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrezkys%2FSwiftProfileMatching/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrezkys%2FSwiftProfileMatching/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrezkys%2FSwiftProfileMatching/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrezkys","download_url":"https://codeload.github.com/mrezkys/SwiftProfileMatching/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrezkys%2FSwiftProfileMatching/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279007151,"owners_count":26084247,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dss","profile-matching","swift","swiftpackage","swiftpackagemanager"],"created_at":"2025-03-31T13:30:44.867Z","updated_at":"2025-10-11T12:21:51.033Z","avatar_url":"https://github.com/mrezkys.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SwiftProfileMatching\n\nA Swift Package for implementing Profile Matching Method - a popular technique used in Decision Support Systems (DSS).\n\n\n## Overview\n\nProfile Matching is a DSS method that compares alternatives against an ideal profile (criteria) to calculate scores and rankings. It's widely applicable in various decision-making scenarios:\n\n- Employee recruitment and selection\n- Supplier evaluation\n- Product/service selection\n- Technology assessment\n- Performance appraisal\n- Project prioritization\n- Site location selection\n- Scholarship recipient selection\n- And many other multi-criteria decision-making scenarios\n\nThis package provides a complete implementation of the Profile Matching method with flexible configuration options designed to be general-purpose and adaptable to your specific needs.\n\n## Development Status\n\n\u003e ⚠️ **Important Notice**: This library is currently under active development and testing. While the core functionality is implemented, you may encounter bugs or changes to the Package. Use in production environments with caution.\n\u003e\n\u003e **Current Limitations**:\n\u003e - Some components still contain hardcoded values that need to be made configurable\n\u003e - The gap calculation strategy needs more flexibility for custom implementations\n\u003e - Some normalization methods are still being refined\n\u003e\n\u003e We welcome feedback, bug reports, and contributions as we work toward a stable 1.0 release. Please check the issues section for known limitations and planned improvements.\n\n## Features\n\n- Calculate profile matching scores based on core and secondary factors\n- Customizable gap calculation strategies\n- Normalization utilities for input data with multiple methods\n- Configuration options for weights, scoring methods, and score ranges\n- Ranking tools to analyze and present results\n- Comprehensive analysis tools (identifying strengths/weaknesses, influential criteria)\n- Z-score normalization for comparative analysis\n\n## Installation\n\n### Swift Package Manager\n\nAdd the following to your `Package.swift` file:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/yourusername/SwiftProfileMatching.git\", from: \"1.0.0\")\n]\n```\n\nOr add it directly in Xcode:\n1. File \u003e Add Packages...\n2. Enter the repository URL: `https://github.com/yourusername/SwiftProfileMatching.git`\n3. Click \"Add Package\"\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch2\u003eQuick Start\u003c/h2\u003e\u003c/summary\u003e\n\nHere's a quick example of how to use the package:\n\n```swift\nimport SwiftProfileMatching\n\n// 1. Define your criteria\nlet criteria = [\n    ProfileMatching.Criterion(name: \"Experience\", targetValue: 4.0, type: .coreFactor, weight: 60),\n    ProfileMatching.Criterion(name: \"Education\", targetValue: 5.0, type: .coreFactor, weight: 40),\n    ProfileMatching.Criterion(name: \"Communication\", targetValue: 3.0, type: .secondaryFactor, weight: 70),\n    ProfileMatching.Criterion(name: \"Teamwork\", targetValue: 4.0, type: .secondaryFactor, weight: 30)\n]\n\n// 2. Create alternatives to evaluate\nlet alternatives = [\n    ProfileMatching.Alternative(\n        id: \"A001\", \n        name: \"Candidate One\", \n        criteriaValues: [\n            \"Experience\": 5.0,\n            \"Education\": 5.0,\n            \"Communication\": 4.0,\n            \"Teamwork\": 5.0\n        ]\n    ),\n    ProfileMatching.Alternative(\n        id: \"A002\", \n        name: \"Candidate Two\", \n        criteriaValues: [\n            \"Experience\": 3.0,\n            \"Education\": 5.0,\n            \"Communication\": 5.0,\n            \"Teamwork\": 3.0\n        ]\n    )\n]\n\n// 3. Create the profile matching calculator with default configuration\n// (Uses .continuous(type: .standard) gap calculation)\nlet profileMatching = ProfileMatching(criteria: criteria)\n\n// 4. Alternatively, create with a custom configuration\n// let config = ProfileMatchingConfiguration(\n//     gapCalculationStrategy: .continuous(type: .standard),\n//     coreFactorWeight: 0.6,\n//     secondaryFactorWeight: 0.4\n// )\n// let profileMatching = ProfileMatching(criteria: criteria, configuration: config)\n\n// 5. Calculate matching results\nlet results = profileMatching.calculateMatching(for: alternatives)\n\n// 6. Print detailed results for top candidate\nprint(\"Detailed results for \\(results[0].alternative.name):\")\nprint(\"- Core Factor Score: \\(results[0].coreFactorScore)\")\nprint(\"- Secondary Factor Score: \\(results[0].secondaryFactorScore)\")\nprint(\"- Final Score: \\(results[0].finalScore)\")\nprint(\"- Gap Details:\")\nfor (criterion, score) in results[0].gapDetails {\n    print(\"  - \\(criterion): \\(score)\")\n}\n\n// 7. Generate a ranking report\nlet report = RankingHelper.createReport(\n    from: results, \n    scoreFormat: .percentage\n)\n\n// 8. Print the summary\nprint(\"\\nFinal ranking:\")\nfor (index, result) in results.enumerated() {\n    print(\"\\(index + 1). \\(result.alternative.name): \\(result.finalScore)\")\n}\n```\n\nThe output will look something like:\n\n```\nDetailed results for Candidate One:\n- Core Factor Score: 4.7\n- Secondary Factor Score: 4.75\n- Final Score: 4.72\n- Gap Details:\n  - Experience: 4.5\n  - Education: 5.0\n  - Communication: 4.75\n  - Teamwork: 4.75\n\nFinal ranking:\n1. Candidate One: 4.72\n2. Candidate Two: 4.41\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch2\u003eAdvanced Usage\u003c/h2\u003e\u003c/summary\u003e\n\u003cimg width=\"782\" alt=\"Screenshot 2025-03-18 at 16 26 18\" src=\"https://github.com/user-attachments/assets/760fa3f0-5c7f-4e8e-ac4d-5f723ab8f0e8\" /\u003e\n\n### Custom Configuration\n\nYou can customize the profile matching calculation with a configuration:\n\n```swift\nlet config = ProfileMatchingConfiguration(\n    gapCalculationStrategy: .continuous(type: .custom(\n        perfectMatchScore: 10.0,\n        exceedsPenalty: 1.0,\n        belowPenalty: 2.0,\n        maxScore: 10.0\n    )),\n    coreFactorWeight: 0.7,\n    secondaryFactorWeight: 0.3,\n    normalizationMethod: .global,\n    weightCalculation: .normalized,\n    scoreRange: (0.0, 10.0)\n)\n\nlet profileMatching = ProfileMatching(criteria: criteria, configuration: config)\n```\n\n### Continuous Gap Calculation\n\nFor scenarios where you need formula-based gap calculations with smooth transitions between values, use the continuous gap calculation strategies:\n\n```swift\n// 1. Standard method (default)\n// Different formulas for core and secondary factors\nlet standardConfig = ProfileMatchingConfiguration(\n    gapCalculationStrategy: .continuous(type: .standard),\n    // Other parameters...\n)\n\n// 2. Simple method\n// Uses normalized absolute difference\nlet simpleConfig = ProfileMatchingConfiguration(\n    gapCalculationStrategy: .continuous(type: .simple),\n    // Other parameters...\n)\n\n// 3. Custom method with configurable parameters\nlet customConfig = ProfileMatchingConfiguration(\n    gapCalculationStrategy: .continuous(type: .custom(\n        perfectMatchScore: 10.0,  // Score for perfect match (gap = 0)\n        exceedsPenalty: 1.0,      // Penalty per unit when exceeding target\n        belowPenalty: 2.0,        // Penalty per unit when below target\n        maxScore: 10.0            // Maximum possible score\n    )),\n    // Other parameters...\n)\n```\n\nUnderstanding continuous calculation types:\n\n1. **Standard**: Uses the classic Profile Matching formula with different calculations for core vs. secondary factors:\n   - For core factors: Stricter penalties for missing targets\n   - For secondary factors: More lenient penalties\n   - Perfect match always gets 5.0\n\n2. **Simple**: Uses normalized absolute difference:\n   - Formula: 5.0 - min(5.0, abs(gap))\n   - Provides a linear penalty based on distance from target\n   - Good for simple cases where you want equal penalties for exceeding or falling below target\n\n3. **Custom**: Fully configurable formula with parameters:\n   - `perfectMatchScore`: Score assigned for exact matches (gap = 0)\n   - `exceedsPenalty`: Penalty per unit when exceeding target (gap \u003e 0)\n   - `belowPenalty`: Penalty per unit when below target (gap \u003c 0)\n   - `maxScore`: Maximum possible score cap\n\nExample scores using different continuous methods for gap value Δ:\n\n```\nGap (Δ)  | Standard (Core) | Standard (Secondary) | Simple | Custom (10,1,2,10)\n---------|----------------|---------------------|--------|------------------\n  -2.0   |     3.0        |        3.5          |   3.0  |       6.0\n  -1.0   |     4.0        |        4.25         |   4.0  |       8.0\n   0.0   |     5.0        |        5.0          |   5.0  |      10.0\n  +1.0   |     4.5        |        4.75         |   4.0  |       9.0\n  +2.0   |     4.0        |        4.5          |   3.0  |       8.0\n```\n\n### Discrete Gap Calculation\n\nFor scenarios where gap values should map to discrete scores (like categorical data), use the discrete gap calculation strategy with various handling methods:\n\n```swift\n// Create gap-score mapping from a dictionary\nlet gapScoreMap: [Double: Double] = [\n    -3.0: 1.0,  // Far below target\n    -2.0: 2.0,  // Below target\n    -1.0: 3.5,  // Slightly below target\n     0.0: 5.0,  // Perfect match\n     1.0: 4.0,  // Slightly above target\n     2.0: 3.0,  // Above target\n     3.0: 2.0   // Far above target\n]\n\nlet mappingPairs = ProfileMatchingConfiguration.createDiscreteMapping(gapToScoreMap: gapScoreMap)\n\n// Create configuration with different handling methods for undefined gaps:\n\n// 1. Basic method (nearest neighbor approach)\nlet basicConfig = ProfileMatchingConfiguration(\n    gapCalculationStrategy: .discrete(mappingPairs: mappingPairs),  // Uses .basic by default\n    // Other parameters...\n)\n\n// 2. Linear interpolation between defined points\nlet interpolationConfig = ProfileMatchingConfiguration(\n    gapCalculationStrategy: .discrete(mappingPairs: mappingPairs, handlingMethod: .interpolation),\n    // Other parameters...\n)\n\n// 3. Nearest neighbor (finds closest defined gap)\nlet nearestConfig = ProfileMatchingConfiguration(\n    gapCalculationStrategy: .discrete(mappingPairs: mappingPairs, handlingMethod: .nearestNeighbor),\n    // Other parameters...\n)\n\n// 4. Threshold method (uses defined point as the start of a range)\nlet thresholdConfig = ProfileMatchingConfiguration(\n    gapCalculationStrategy: .discrete(mappingPairs: mappingPairs, handlingMethod: .threshold),\n    // Other parameters...\n)\n\n// 5. Default value (uses specified score for undefined gaps)\nlet defaultConfig = ProfileMatchingConfiguration(\n    gapCalculationStrategy: .discrete(mappingPairs: mappingPairs, handlingMethod: .defaultValue(score: 2.0)),\n    // Other parameters...\n)\n\n// Or use convenience methods with handling method:\n\n// Symmetric scoring (same penalty for above/below target)\nlet symmetricConfig = ProfileMatchingConfiguration.discreteSymmetric(\n    perfectMatchScore: 5.0,\n    maxGap: 5,\n    maxScore: 5.0,\n    handlingMethod: .threshold  // Choose a handling method\n)\n\n// Asymmetric scoring (different penalties for above/below target)\nlet asymmetricConfig = ProfileMatchingConfiguration.discreteAsymmetric(\n    perfectMatchScore: 5.0,\n    maxGap: 5,\n    exceedPenalty: 0.5,   // Lower penalty for exceeding target\n    belowPenalty: 1.0,    // Higher penalty for falling below target\n    maxScore: 5.0,\n    handlingMethod: .interpolation  // Choose a handling method\n)\n```\n\nUnderstanding gap handling methods:\n\n1. **Basic**: Simple nearest neighbor approach - finds closest defined gap value.\n2. **Interpolation**: Uses linear interpolation between nearest defined points (good for smooth transitions).\n3. **Nearest Neighbor**: Explicitly finds the closest defined gap by distance.\n4. **Threshold**: Each defined gap value represents the start of a range up to the next defined gap.\n5. **Default Value**: Uses a specified default score for any undefined gaps.\n\nExample of different methods on an undefined gap value:\n\n```swift\n// For a gap of -1.5 with defined mappings at -2.0 (score 2.0) and -1.0 (score 3.5):\n// - Interpolation:    2.75 (halfway between 2.0 and 3.5)\n// - Nearest Neighbor: 2.0  (closer to -2.0 than -1.0)\n// - Threshold:        2.0  (falls within range starting at -2.0)\n// - Default Value:    1.0  (uses the specified default value)\n```\n\n### Normalizing Input Values\n\nUse the Normalization utilities to standardize raw input values:\n\n```swift\n// Normalize values from different scales to the standard 0-5 scale\nlet rawValues = [\"Criterion1\": 75.0, \"Criterion2\": 80.0]\n\nlet ranges = [\n    \"Criterion1\": (min: 0.0, max: 100.0),\n    \"Criterion2\": (min: 0.0, max: 100.0)\n]\n\nlet normalized = Normalization.normalizeCriteriaValues(\n    rawValues, \n    criteriaRanges: ranges,\n    targetMin: 0.0,\n    targetMax: 5.0\n)\n// Result: [\"Criterion1\": 3.75, \"Criterion2\": 4.0]\n\n// Compare alternatives using Z-scores\nlet scores = [\"A001\": 4.5, \"A002\": 3.8, \"A003\": 4.1]\nlet zScores = Normalization.normalizeToZScores(scores)\n// Compares each alternative's performance relative to the mean\n```\n\n### Analysis Tools\n\nIdentify strengths and weaknesses of alternatives:\n\n```swift\nlet (strengths, weaknesses) = RankingHelper.identifyStrengthsAndWeaknesses(\n    for: result,\n    threshold: 4.0  // Criteria scored 4.0 or higher are strengths\n)\n\nprint(\"Strengths: \\(strengths)\")\nprint(\"Weaknesses: \\(weaknesses)\")\n```\n\nFind which criteria had the most influence on the results:\n\n```swift\nlet influential = RankingHelper.findMostInfluentialCriteria(from: results)\n\n// Measure how well each criterion differentiates between alternatives\nlet differentiationPower = RankingHelper.calculateCriteriaDifferentiationPower(from: results)\n```\n\n### Formatting Results\n\nFormat scores in different ways:\n\n```swift\n// Create reports with different formatting options\nlet rawReport = RankingHelper.createReport(from: results, scoreFormat: .raw)\nlet percentReport = RankingHelper.createReport(from: results, scoreFormat: .percentage)\nlet starsReport = RankingHelper.createReport(from: results, scoreFormat: .stars(maxStars: 5))\n\n// Format a specific score\nprint(rawReport.formatScore(4.5))      // \"4.50\"\nprint(percentReport.formatScore(4.5))  // \"90.0%\"\nprint(starsReport.formatScore(4.5))    // \"⭐⭐⭐⭐⭐\"\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch2\u003eTheory\u003c/h2\u003e\u003c/summary\u003e\n\n### Profile Matching Fundamentals\n\nProfile Matching is a multi-criteria decision-making method used to evaluate alternatives by comparing their attribute values against an ideal profile. The process involves several key steps:\n\n1. **Define criteria and target values** - Establish what factors are important and what their ideal values should be\n2. **Categorize criteria** - Typically divided into \"core factors\" (essential criteria) and \"secondary factors\" (supporting criteria)\n3. **Assign weights** - Determine the relative importance of each criterion\n4. **Optional: Normalize input values** - Standardize values across different scales (optional, disabled by default)\n5. **Calculate gap values** - Measure how closely each alternative matches the ideal profile for each criterion\n6. **Calculate weighted scores** - Combine gap values using appropriate weighting schemes\n7. **Rank alternatives** - Order alternatives based on their final scores\n\n### Step-by-Step Process\n\nLet's walk through a complete Profile Matching calculation using this package, assuming a job candidate selection scenario:\n\n#### 1. Define Criteria and Target Values\n\nFirst, we define what we're looking for by specifying criteria and their ideal values:\n\n```swift\nlet criteria = [\n    ProfileMatching.Criterion(name: \"Experience\", targetValue: 4.0, type: .coreFactor, weight: 60),\n    ProfileMatching.Criterion(name: \"Education\", targetValue: 5.0, type: .coreFactor, weight: 40),\n    ProfileMatching.Criterion(name: \"Communication\", targetValue: 3.0, type: .secondaryFactor, weight: 70),\n    ProfileMatching.Criterion(name: \"Teamwork\", targetValue: 4.0, type: .secondaryFactor, weight: 30)\n]\n```\n\nIn this example:\n- We have 4 criteria: Experience, Education, Communication, and Teamwork\n- Values are on a scale of 0-5\n- Experience and Education are core factors (essential requirements)\n- Communication and Teamwork are secondary factors (desirable but not critical)\n\n#### 2. Create Alternatives to Evaluate\n\nNext, we define our alternatives to evaluate:\n\n```swift\nlet alternatives = [\n    ProfileMatching.Alternative(\n        id: \"A001\", \n        name: \"John Smith\", \n        criteriaValues: [\n            \"Experience\": 5.0,  // Exceeds target\n            \"Education\": 5.0,   // Matches target\n            \"Communication\": 4.0, // Exceeds target\n            \"Teamwork\": 5.0     // Exceeds target\n        ]\n    ),\n    ProfileMatching.Alternative(\n        id: \"A002\", \n        name: \"Jane Doe\", \n        criteriaValues: [\n            \"Experience\": 3.0,  // Below target\n            \"Education\": 5.0,   // Matches target\n            \"Communication\": 5.0, // Exceeds target\n            \"Teamwork\": 3.0     // Below target\n        ]\n    )\n]\n```\n\n#### 3. Configure Profile Matching (Optional)\n\nBy default, the package uses standard settings, but you can optionally configure various aspects including normalization:\n\n```swift\n// Optional: Create a custom configuration with normalization enabled\nlet config = ProfileMatchingConfiguration(\n    gapCalculationStrategy: .continuous(type: .standard),\n    coreFactorWeight: 0.6,\n    secondaryFactorWeight: 0.4,\n    normalizationMethod: .global,  // Enable normalization\n    weightCalculation: .direct,\n    scoreRange: (0.0, 5.0)\n)\n\n// Initialize with custom configuration\nlet profileMatching = ProfileMatching(criteria: criteria, configuration: config)\n\n// Or use default configuration (no normalization)\n// let profileMatching = ProfileMatching(criteria: criteria)\n```\n\n#### 4. Optional: Normalize Input Values\n\nIf normalization is enabled in the configuration (it's disabled by default), the package will automatically normalize input values before gap calculation.\n\n**Mathematical Formula:**\n\n$$\\text{Normalized Value} = \\frac{\\text{Value} - \\text{Min}}{\\text{Max} - \\text{Min}} \\times (\\text{TargetMax} - \\text{TargetMin}) + \\text{TargetMin}$$\n\nSwiftProfileMatching supports three normalization approaches:\n\n1. **No Normalization (`none`)** - Use raw values directly (default)\n2. **Global Normalization (`global`)** - Standardize values across all alternatives\n3. **Local Normalization (`local`)** - Normalize each criterion separately\n\nWhen enabled, the package internally performs normalization:\n\n```swift\n// This happens internally if normalization is enabled\n// Excerpt from calculateMatchingForAlternative() method\nlet processedValues = normalizeInputValues(alternative.criteriaValues)\n```\n\nIn our default example, normalization is skipped since we're using the same 0-5 scale for all criteria.\n\n#### 5. Calculate Gap Values\n\nFor each criterion, we calculate how closely the alternative matches the target.\n\n**Mathematical Formula (Standard Method):**\n\nFor core factors:\n\nFor perfect match (Δ = 0):\n$$g_i = 5.0$$\n\nFor exceeding target (Δ \u003e 0):\n$$g_i = \\min(4.5, 5.0 - 0.5 \\times \\Delta)$$\n\nFor below target (Δ \u003c 0):\n$$g_i = \\max(0, 5.0 + \\Delta)$$\n\nFor secondary factors:\n\nFor perfect match (Δ = 0):\n$$g_i = 5.0$$\n\nFor exceeding target (Δ \u003e 0):\n$$g_i = \\min(5.0, 5.0 - 0.25 \\times \\Delta)$$\n\nFor below target (Δ \u003c 0):\n$$g_i = \\max(0, 5.0 + 0.75 \\times \\Delta)$$\n\nWhere $\\Delta = \\text{Actual Value} - \\text{Target Value}$\n\n**Custom Gap Calculation Method:**\n\nFor perfect match (Δ = 0):\n$$g_i = P$$\n\nFor exceeding target (Δ \u003e 0):\n$$g_i = \\max(0, \\min(M, P - E \\times \\Delta))$$\n\nFor below target (Δ \u003c 0):\n$$g_i = \\max(0, \\min(M, P + B \\times \\Delta))$$\n\nWhere:\n- $P$ = Perfect match score\n- $E$ = Exceeds penalty\n- $B$ = Below penalty\n- $M$ = Maximum possible score\n- $\\Delta = \\text{Actual Value} - \\text{Target Value}$\n\n**Discrete Gap Calculation Method:**\n\nInstead of using continuous functions, this method uses explicit gap-to-score mappings:\n\n$$g_i = \\text{score from mapping table where gap} = \\Delta$$\n\nFor gaps between defined values, linear interpolation is used:\n\n$$g_i = s_1 + (s_2 - s_1) \\times \\frac{\\Delta - g_1}{g_2 - g_1}$$\n\nWhere:\n- $s_1, s_2$ = Scores for the two closest defined gaps\n- $g_1, g_2$ = Gap values that bracket the actual gap\n- $\\Delta = \\text{Actual Value} - \\text{Target Value}$\n\nThis allows for arbitrary and non-linear scoring patterns that can't be expressed with simple mathematical formulas.\n\nFor John Smith:\n- Experience: Target 4.0, Actual 5.0, Gap = +1.0\n  - Core factor exceeding target: 5.0 - (0.5 × 1.0) = 4.5\n- Education: Target 5.0, Actual 5.0, Gap = 0\n  - Perfect match: 5.0\n- Communication: Target 3.0, Actual 4.0, Gap = +1.0\n  - Secondary factor exceeding target: 5.0 - (0.25 × 1.0) = 4.75\n- Teamwork: Target 4.0, Actual 5.0, Gap = +1.0\n  - Secondary factor exceeding target: 5.0 - (0.25 × 1.0) = 4.75\n\nFor Jane Doe:\n- Experience: Target 4.0, Actual 3.0, Gap = -1.0\n  - Core factor below target: 5.0 + (-1.0) = 4.0\n- Education: Target 5.0, Actual 5.0, Gap = 0\n  - Perfect match: 5.0\n- Communication: Target 3.0, Actual 5.0, Gap = +2.0\n  - Secondary factor exceeding target: 5.0 - (0.25 × 2.0) = 4.5\n- Teamwork: Target 4.0, Actual 3.0, Gap = -1.0\n  - Secondary factor below target: 5.0 + (0.75 × -1.0) = 4.25\n\nThese calculations are performed by the `calculateGap()` method:\n\n```swift\n// The package internally calculates the gap values\nfor criterion in criteria {\n    if let value = processedValues[criterion.name] {\n        let gap = calculateGap(targetValue: criterion.targetValue, \n                              actualValue: value, \n                              type: criterion.type)\n        gapDetails[criterion.name] = gap\n    }\n}\n```\n\n#### 6. Calculate Core Factor and Secondary Factor Scores\n\nNext, we calculate weighted scores for core and secondary factors separately.\n\n**Mathematical Formula:**\n\n$$\\text{Factor Score} = \\frac{\\sum_{i=1}^{n} (w_i \\times g_i)}{\\sum_{i=1}^{n} w_i}$$\n\nWhere:\n- $w_i$ = Weight of criterion $i$ (in decimal form)\n- $g_i$ = Gap value for criterion $i$\n- $n$ = Number of criteria in the factor group\n\nFor John Smith:\n- Core Factor Score: \n  - Experience (weight 60%): 4.5\n  - Education (weight 40%): 5.0\n  - Weighted Average: (4.5 × 0.6) + (5.0 × 0.4) = 4.7\n- Secondary Factor Score:\n  - Communication (weight 70%): 4.75\n  - Teamwork (weight 30%): 4.75\n  - Weighted Average: (4.75 × 0.7) + (4.75 × 0.3) = 4.75\n\nFor Jane Doe:\n- Core Factor Score:\n  - Experience (weight 60%): 4.0\n  - Education (weight 40%): 5.0\n  - Weighted Average: (4.0 × 0.6) + (5.0 × 0.4) = 4.4\n- Secondary Factor Score:\n  - Communication (weight 70%): 4.5\n  - Teamwork (weight 30%): 4.25\n  - Weighted Average: (4.5 × 0.7) + (4.25 × 0.3) = 4.425\n\nThe package automatically handles these weighted calculations:\n\n```swift\n// Calculated by the package through the calculateWeightedScore() method\nlet coreFactorScore = calculateWeightedScore(for: coreFactors, using: gapDetails)\nlet secondaryFactorScore = calculateWeightedScore(for: secondaryFactors, using: gapDetails)\n```\n\n#### 7. Calculate Final Scores\n\nFinally, we combine core and secondary factor scores with their respective weights.\n\n**Mathematical Formula:**\n\n$$\\text{Final Score} = (W_{cf} \\times \\text{CF}) + (W_{sf} \\times \\text{SF})$$\n\nWhere:\n- $W_{cf}$ = Weight for core factors (typically 0.6 or 60%)\n- $\\text{CF}$ = Core factor score\n- $W_{sf}$ = Weight for secondary factors (typically 0.4 or 40%)\n- $\\text{SF}$ = Secondary factor score\n\nFor John Smith:\n- Final Score: (4.7 × 0.6) + (4.75 × 0.4) = 2.82 + 1.9 = 4.72\n\nFor Jane Doe:\n- Final Score: (4.4 × 0.6) + (4.425 × 0.4) = 2.64 + 1.77 = 4.41\n\nThe package performs this calculation:\n\n```swift\n// Calculated by the package\nfinalScore = (configuration.coreFactorWeight * coreFactorScore) +\n             (configuration.secondaryFactorWeight * secondaryFactorScore)\n```\n\n#### 8. Rank and Analyze\n\nThe package then sorts alternatives by final score:\n\n```swift\n// John Smith: 4.72\n// Jane Doe: 4.41\nlet results = profileMatching.calculateMatching(for: alternatives)\n// results[0] would be John Smith\n// results[1] would be Jane Doe\n```\n\nFor deeper analysis, we can:\n\n```swift\n// Generate a formatted report\nlet report = RankingHelper.createReport(from: results)\n\n// Find strengths and weaknesses\nlet (strengths, weaknesses) = RankingHelper.identifyStrengthsAndWeaknesses(\n    for: results[0],  // John Smith\n    threshold: 4.5    // Criteria with scores above 4.5 are strengths\n)\n// strengths = [\"Education\", \"Communication\", \"Teamwork\"]\n// weaknesses = []\n\n// Identify most influential criteria\nlet influential = RankingHelper.findMostInfluentialCriteria(from: results)\n// Would show which criteria created the most differentiation\n```\n\n### Core vs. Secondary Factors\n\nProfile Matching distinguishes between different types of criteria:\n\n- **Core Factors** - Essential criteria that are critical to the decision (e.g., education level for a job position)\n- **Secondary Factors** - Supporting criteria that are beneficial but not critical (e.g., communication skills)\n\nThese are implemented as the `GapType` enum:\n\n```swift\npublic enum GapType: Sendable {\n    case coreFactor\n    case secondaryFactor\n}\n```\n\n### Weighted Scoring\n\nOnce gap values are calculated, they are combined using weighted averages. The package supports two weighting approaches:\n\n1. **Direct Weights** - Use weights as provided (must sum to 100%)\n2. **Normalized Weights** - Automatically normalize weights to ensure they sum to 100%\n\n```swift\n// Implemented in calculateWeightedScore()\nvar totalWeight = 0.0\nvar weightedSum = 0.0\n\nfor criterion in criteria {\n    guard let gapValue = gapDetails[criterion.name] else { continue }\n    \n    // Convert percentage weights to decimal\n    let normalizedWeight = criterion.weight / 100.0\n    weightedSum += normalizedWeight * gapValue\n    totalWeight += normalizedWeight\n}\n\n// Normalize by total weight\nreturn totalWeight \u003e 0 ? weightedSum / totalWeight : 0\n```\n\n### Ranking and Analysis\n\nAfter scoring alternatives, additional analysis can provide deeper insights:\n\n1. **Influential Criteria Analysis** - Identify which criteria had the most impact on differentiation\n2. **Strengths and Weaknesses** - Highlight areas where alternatives perform particularly well or poorly\n3. **Z-Score Analysis** - Compare alternatives against the statistical distribution of all alternatives\n\n```swift\n// Example of differentiation power calculation\npublic static func calculateCriteriaDifferentiationPower(from results: [MatchingResult]) -\u003e [String: Double] {\n    // Find all unique criteria\n    var allCriteria = Set\u003cString\u003e()\n    for result in results {\n        allCriteria.formUnion(result.gapDetails.keys)\n    }\n    \n    // Calculate standard deviation for each criterion\n    var differentiationPower = [String: Double]()\n    \n    for criterion in allCriteria {\n        let values = results.compactMap { $0.gapDetails[criterion] }\n        guard values.count \u003e 1 else {\n            differentiationPower[criterion] = 0.0\n            continue\n        }\n        \n        // Calculate standard deviation\n        let mean = values.reduce(0.0, +) / Double(values.count)\n        let variance = values.reduce(0.0) { sum, value in\n            let diff = value - mean\n            return sum + (diff * diff)\n        } / Double(values.count)\n        \n        let stdDev = sqrt(variance)\n        differentiationPower[criterion] = stdDev\n    }\n    \n    return differentiationPower\n}\n```\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrezkys%2Fswiftprofilematching","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrezkys%2Fswiftprofilematching","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrezkys%2Fswiftprofilematching/lists"}