diff --git a/gross-to-net/README.md b/gross-to-net/README.md
index 43a0eb6..d49a514 100644
--- a/gross-to-net/README.md
+++ b/gross-to-net/README.md
@@ -1,84 +1,75 @@
-# Gross-to-net Pay Check Coding Challenge
-
-Requirements:
-
-- Calculate the net pay as follows: net pay = taxable earnings - pre-tax deductions - taxes - post-tax deductions + non-taxable earnings
-- Earning types
- - Salary earnings are defined by a flat `amount`
- - Hourly earnings are defined by an amount of `hours` and a `rate`
-- Earnings that are not taxable are not withheld by deductions or taxes
-- Pre-tax deductions are based on gross taxable-pay
-- Flat vs percentage deductions (percentages encoded as `value / 100`)
-- Taxes are based on gross taxable-pay less pre-tax deductions
-- Taxes with a cap should not be withheld above the cap
-- Post-tax deductions are based on gross taxable-pay less pre-tax deductions and taxes
-- Deduction and tax priority
- - When taxable earnings are less than deductions and taxes, withholding priority is as follows: pre-tax deductions before taxes before post-tax deductions.
- - For each deduction and tax, a higher `priority` value indicates this item should be withheld first.
- - If a deduction or tax is cannot be withheld to the full amount, the deficit is recorded.
-- Output the following
- - Gross pay
- - Net pay to employee
- - Amounts withheld for deductions and taxes by code with deficits
-
-Sample input object:
-
-```json
-{
- "earnings": [
- {
- "code": "overtime",
- "type": "hourly",
- "hours": 20,
- "rate": 15,
- "isTaxable": true
- },
- {
- "code": "regular",
- "type": "hourly",
- "hours": 20,
- "rate": 13,
- "isTaxable": false
- },
- {
- "code": "bonus",
- "type": "salary",
- "amount": 1500,
- "isTaxable": true
- }
- ],
- "deductions": [
- {
- "code": "401k",
- "type": "percentage",
- "priority": 1,
- "value": 10,
- "isPreTax": false
- },
- {
- "code": "healthInsurance",
- "type": "flat",
- "priority": 2,
- "value": 20,
- "isPreTax": true
- }
- ],
- "taxes": [
- {
- "code": "federalIncome",
- "type": "percentage",
- "priority": 1,
- "value": 10
- },
- {
- "code": "fica",
- "type": "cappedPercentage",
- "priority": 2,
- "value": 10,
- "cap": 50
- }
- ]
-}
+# Gross-to-Net Payroll Calculator
+
+Implement the `Calculate` method in `PayrollCalculator.cs` which accepts three collections as arguments... `earnings`, `deductions`, and `taxes` then returns a `PayrollResult` with `grossPay`, `grossTaxableEarnings`, a list of `witholdings`, and `netPay`.
+
+## Requirements
+
+### NetPay Calculation
+
+`NetPay = GrossTaxableEarnings - PreTaxDeductions - Taxes - PostTaxDeductions + NonTaxableEarnings`
+
+### Earnings
+
+- Types: `salary`, `hourly`
+- Salary earnings have a flat `amount`
+- Hourly earnings amount is `hours × rate`
+- Only taxable earnings are included when calculating deductions/taxes
+
+### Deductions
+
+- Types: `flat`, `percentage`
+- Flat decductions have a fixed dollar `value`
+- Percentage deductions are calculated as `value / 100` of basis
+- Pre-tax deductions reduce tax basis, calculated with gross taxable earnings
+- Post-tax deductions do not reduce tax basis
+
+### Taxes
+
+- Types: `percentage`, `cappedPercentage`
+- Percentage taxes are calculated as `value / 100` of basis using (gross taxable earnings - pre-tax deductions)
+- Capped Percentage has a max value defined by the `cap` property
+
+### Witholdings
+
+- Types: `tax`, `deductions`
+- Deductions are either Pre-Tax or Post-Tax depending on the `isPrefix` value.
+- Witholding groups are applied using the following order:
+ - PreTax Deductions
+ - Taxes
+ - PostTax Deductions
+- Within each group, withholdings are applied in order of their `priority` value (highest `priority` first)
+- When a withholding exceeds available earnings the difference is added as a `deficit` as NetPay cannot go below zero.
+
+## Build and Run
+
+To test your solution, Program.main() contains a test harness for loading test data from a json file and comparing the expected output to the actual output provided by your `Calculate` method and report the result.
+
+A json file containing basic test cases is included in the project, `test-cases.json`, but you are encouraged to add additional test cases as needed. Additional test cases may be used to verify edge cases and common defects during your interview.
+
+### IDE
+
+An online VSCode codespace will be used during assesmments and candidates are free to install extensions available in the vs code default extension marketplace.
+
+Start Debug: `F5`
+
+### Commmand-line
+
+Navigate to the project root e.g. `gross-to-net/dotnet/` then use _dotnet_ CLI commands.
+
+Building from CLI
+
+```ps
+dotnet build
+```
+
+Running from CLI
+
+```ps
+dotnet run
```
-All test cases and solutions can be found in `test-cases.json`
\ No newline at end of file
+Show Help Text
+
+```ps
+dotnet run -- --help
+```
\ No newline at end of file
diff --git a/gross-to-net/directory-structure.txt b/gross-to-net/directory-structure.txt
new file mode 100644
index 0000000..d415746
--- /dev/null
+++ b/gross-to-net/directory-structure.txt
@@ -0,0 +1,64 @@
+Folder PATH listing for volume Windows
+Volume serial number is 7EA5-7906
+C:.
+| advanced-test-cases.json
+| directory-structure.txt
+| input.json
+| INTERVIEWER_README.md
+| README.md
+| test-cases.json
+|
+\---dotnet
+ | GrossToNet.csproj
+ | PayrollCalculator.cs
+ | Program.cs
+ |
+ +---bin
+ | \---Debug
+ | \---net8.0
+ | advanced-test-cases.json
+ | GrossToNet.deps.json
+ | GrossToNet.dll
+ | GrossToNet.exe
+ | GrossToNet.pdb
+ | GrossToNet.runtimeconfig.json
+ | input.json
+ | test-cases.json
+ |
+ +---models
+ | Deduction.cs
+ | Earning.cs
+ | PayrollData.cs
+ | PayrollResult.cs
+ | Tax.cs
+ | Withholding.cs
+ |
+ \---obj
+ | GrossToNet.csproj.nuget.dgspec.json
+ | GrossToNet.csproj.nuget.g.props
+ | GrossToNet.csproj.nuget.g.targets
+ | project.assets.json
+ | project.nuget.cache
+ |
+ \---Debug
+ \---net8.0
+ | .NETCoreApp,Version=v8.0.AssemblyAttributes.cs
+ | apphost.exe
+ | GrossToNet.AssemblyInfo.cs
+ | GrossToNet.AssemblyInfoInputs.cache
+ | GrossToNet.assets.cache
+ | GrossToNet.csproj.CoreCompileInputs.cache
+ | GrossToNet.csproj.FileListAbsolute.txt
+ | GrossToNet.dll
+ | GrossToNet.GeneratedMSBuildEditorConfig.editorconfig
+ | GrossToNet.genruntimeconfig.cache
+ | GrossToNet.GlobalUsings.g.cs
+ | GrossToNet.pdb
+ | GrossToNet.sourcelink.json
+ |
+ +---ref
+ | GrossToNet.dll
+ |
+ \---refint
+ GrossToNet.dll
+
diff --git a/gross-to-net/dotnet/GrossToNet.csproj b/gross-to-net/dotnet/GrossToNet.csproj
index 035ab5d..a53762f 100644
--- a/gross-to-net/dotnet/GrossToNet.csproj
+++ b/gross-to-net/dotnet/GrossToNet.csproj
@@ -7,9 +7,10 @@
enable
enable
+
-
- PreserveNewest
+
+ Always
-
-
+
+
\ No newline at end of file
diff --git a/gross-to-net/dotnet/PayrollCalculator.cs b/gross-to-net/dotnet/PayrollCalculator.cs
new file mode 100644
index 0000000..0b11e3b
--- /dev/null
+++ b/gross-to-net/dotnet/PayrollCalculator.cs
@@ -0,0 +1,20 @@
+using GrossToNet.Models;
+
+namespace GrossToNet;
+
+///
+/// Payroll calculator that processes earnings, deductions, and taxes to compute net pay.
+///
+/// CALCULATION: Net pay = gross pay - pre-tax deductions - taxes - post-tax deductions
+///
+/// See README.md for detailed requirements and examples.
+///
+public class PayrollCalculator
+{
+ public PayrollResult Calculate(IEnumerable earnings, IEnumerable deductions, IEnumerable taxes)
+ {
+
+ // TODO: Implement payroll calculation logic here
+ throw new NotImplementedException("Payroll calculation not yet implemented. Please implement the Calculate method.");
+ }
+}
\ No newline at end of file
diff --git a/gross-to-net/dotnet/Program.cs b/gross-to-net/dotnet/Program.cs
index 0c8b6bf..b12551a 100644
--- a/gross-to-net/dotnet/Program.cs
+++ b/gross-to-net/dotnet/Program.cs
@@ -1,13 +1,223 @@
-namespace GrossToNet;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using GrossToNet.Models;
+
+namespace GrossToNet;
class Program
{
- private static string InputFilePath => Path.Combine(AppContext.BaseDirectory, "input.json");
+ private const string TestFile = "test-cases.json";
+ static void Main(string[] args)
+ {
+ // Check for help argument
+ if (args.Any(arg => arg == "-h" || arg == "--help" || arg == "-?"))
+ {
+ DisplayHelp();
+ Environment.Exit(0);
+ }
+
+ Console.WriteLine("=".PadRight(70, '='));
+ Console.WriteLine("GROSS-TO-NET PAYROLL CALCULATOR - TEST RUNNER");
+ Console.WriteLine("=".PadRight(70, '='));
+ Console.WriteLine();
+
+ // Check for custom file argument
+ string? customFileName = null;
+ for (int i = 0; i < args.Length; i++)
+ {
+ if ((args[i] == "-f" || args[i] == "--file") && i + 1 < args.Length)
+ {
+ customFileName = args[i + 1];
+ break;
+ }
+ }
+
+ string testFile;
+ bool useAdvancedTests = false;
+
+ if (customFileName != null)
+ {
+ testFile = customFileName;
+ }
+ else
+ {
+ const string AdvancedTestCases = "advanced-" + TestFile;
+ var advancedFilePath = Path.Combine(AppContext.BaseDirectory, AdvancedTestCases);
+ useAdvancedTests = File.Exists(advancedFilePath);
+ testFile = useAdvancedTests ? AdvancedTestCases : TestFile;
+ }
+
+ var filePath = Path.Combine(AppContext.BaseDirectory, testFile);
+
+ if (useAdvancedTests)
+ {
+ Console.WriteLine("Running ADVANCED test cases (advanced test file detected).");
+ }
+ else
+ {
+ Console.WriteLine("Running STANDARD test cases.");
+ }
+
+ if (!File.Exists(filePath))
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine($"✗ Test cases file not found: {filePath}");
+ Console.ResetColor();
+ Environment.Exit(1);
+ }
+
+ Console.WriteLine($"Loading " + testFile + $" from: {filePath}");
+ var testCases = JsonSerializer.Deserialize>(File.ReadAllText(filePath), new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ });
+
+ if (testCases == null || !testCases.Any())
+ {
+ throw new Exception("Failed to load payroll data from input.json");
+ }
+
+ Console.WriteLine($"Loaded {testCases.Count()} test cases.");
+
+ PayrollCalculator calculator = new PayrollCalculator();
+
+ foreach (var testCase in testCases)
+ {
+ try
+ {
+ testCase.ActualOutput = calculator.Calculate(testCase.Input.Earnings, testCase.Input.Deductions, testCase.Input.Taxes);
+ }
+ catch (NotImplementedException nex)
+ {
+ testCase.Error = nex;
+ testCase.Passed = false;
+ testCase.Message = "Payroll calculation not yet implemented.";
+ }
+ catch (Exception ex)
+ {
+ testCase.Error = ex;
+ testCase.Passed = false;
+ testCase.Message = $"Calculator threw an exception: {ex.Message}";
+ }
+
+ ValidateTestCase(testCase);
+ }
+
+ var failedTests = testCases.Where(tc => tc.Passed == false).ToList();
+ if (failedTests.Any())
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine($"✗ {failedTests.Count} out of {testCases.Count()} tests failed:");
+ Console.ResetColor();
+ Environment.Exit(1);
+ }
+
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine($"✓ All {testCases.Count()} test cases passed!");
+ Console.ResetColor();
+ Environment.Exit(0);
+
+ }
+
+ private static void ValidateTestCase(TestCase testCase)
+ {
+ var actual = testCase.ActualOutput!;
+
+ if (testCase.Passed == false)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine($"\t ✗ Test failed: {testCase.Description} --> {(testCase.Error != null ? $"{testCase.Error.GetType().Name} --> " : string.Empty)}{testCase.Message}");
+ Console.ResetColor();
+ return;
+ }
+
+ if (testCase.ExpectedOutput == null)
+ {
+ testCase.Passed = false;
+ testCase.Message = "No expected output provided for test case.";
+ return;
+ }
+ var expected = testCase.ExpectedOutput!;
+
+ if (actual.GrossPay == expected.GrossPay
+ && actual.GrossTaxableEarnings == expected.GrossTaxableEarnings
+ && actual.NetPay == expected.NetPay
+ && actual.Withholdings.Count == expected.Withholdings.Count)
+ {
+ testCase.Passed = true;
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine($"✓ Test passed: {testCase.Description}");
+ Console.ResetColor();
+ return;
+ }
- static void Main(string[] args)
+ testCase.Passed = false;
+
+ Console.ForegroundColor = ConsoleColor.Red;
+
+ if (actual.GrossPay != expected.GrossPay)
+ {
+ Console.WriteLine($"\tGrossPay mismatch Actual: {actual.GrossPay} Expected: {expected.GrossPay}");
+ }
+ if (actual.GrossTaxableEarnings != expected.GrossTaxableEarnings)
+ {
+ Console.WriteLine($"\tGrossTaxableEarnings mismatch Actual: {actual.GrossTaxableEarnings} Expected: {expected.GrossTaxableEarnings}");
+ }
+ if (actual.NetPay != expected.NetPay)
+ {
+ Console.WriteLine($"\tNetPay mismatch Actual: {actual.NetPay} Expected: {expected.NetPay}");
+ }
+ if (actual.Withholdings.Count != expected.Withholdings.Count)
{
- // Your code goes here!
-
- throw new NotImplementedException();
+ Console.WriteLine($"\tWithholdings count mismatch Actual: {actual.Withholdings.Count} Expected: {expected.Withholdings.Count}");
}
-}
\ No newline at end of file
+ Console.ResetColor();
+ }
+
+ private static void DisplayHelp()
+ {
+ Console.WriteLine("GROSS-TO-NET PAYROLL CALCULATOR - TEST RUNNER");
+ Console.WriteLine();
+ Console.WriteLine("USAGE:");
+ Console.WriteLine(" dotnet run [options]");
+ Console.WriteLine();
+ Console.WriteLine("OPTIONS:");
+ Console.WriteLine(" -f, --file Specify a custom test file to run");
+ Console.WriteLine(" -h, --help, -? Display this help information");
+ Console.WriteLine();
+ Console.WriteLine("BEHAVIOR:");
+ Console.WriteLine(" - If -f/--file is specified, runs the specified test file");
+ Console.WriteLine(" - Otherwise, checks for 'advanced-test-cases.json' and runs it if found");
+ Console.WriteLine(" - If no advanced file exists, runs 'test-cases.json' by default");
+ Console.WriteLine();
+ Console.WriteLine("EXAMPLES:");
+ Console.WriteLine(" dotnet run");
+ Console.WriteLine(" dotnet run -- -f custom-tests.json");
+ Console.WriteLine(" dotnet run -- --file advanced-test-cases.json");
+ }
+}
+
+public record PayrollData(IEnumerable Earnings, IEnumerable Deductions, IEnumerable Taxes);
+
+// Helper classes for test case deserialization
+public class TestCase
+{
+ [JsonPropertyName("input")]
+ public required PayrollData Input { get; set; }
+
+ [JsonPropertyName("output")]
+ public PayrollResult? ExpectedOutput { get; set; }
+
+ public string? Description { get; set; }
+
+ [JsonIgnore]
+ public bool? Passed { get; set; }
+ [JsonIgnore]
+ public string? Message { get; set; }
+ [JsonIgnore]
+ public Exception? Error { get; set; }
+ [JsonIgnore]
+ public PayrollResult? ActualOutput { get; set; }
+}
+
diff --git a/gross-to-net/dotnet/models/Deduction.cs b/gross-to-net/dotnet/models/Deduction.cs
new file mode 100644
index 0000000..5641316
--- /dev/null
+++ b/gross-to-net/dotnet/models/Deduction.cs
@@ -0,0 +1,35 @@
+namespace GrossToNet.Models;
+
+///
+/// Represents a deduction from income (e.g., 401k, health insurance).
+/// Deductions can be percentage-based or flat amounts, and can be pre-tax or post-tax.
+///
+public class Deduction
+{
+ ///
+ /// Identifier for the deduction type (e.g., "401k", "healthInsurance")
+ ///
+ public required string Code { get; set; }
+
+ ///
+ /// The deduction calculation type: "percentage" or "flat"
+ ///
+ public required string Type { get; set; }
+
+ ///
+ /// Processing priority; lower values are processed first.
+ /// Determines order of deduction application.
+ ///
+ public required int Priority { get; set; }
+
+ ///
+ /// The deduction value: percentage (0-100) or flat amount
+ ///
+ public required decimal Value { get; set; }
+
+ ///
+ /// Whether this deduction is applied before taxes (pre-tax) or after (post-tax)
+ ///
+ public required bool IsPreTax { get; set; }
+
+}
diff --git a/gross-to-net/dotnet/models/Earning.cs b/gross-to-net/dotnet/models/Earning.cs
new file mode 100644
index 0000000..a646c2d
--- /dev/null
+++ b/gross-to-net/dotnet/models/Earning.cs
@@ -0,0 +1,38 @@
+namespace GrossToNet.Models;
+
+///
+/// Represents an earning item that contributes to gross income.
+/// Can be hourly-based (calculated from hours and rate) or salary-based (flat amount).
+///
+public class Earning
+{
+ ///
+ /// Identifier for the earning type (e.g., "overtime", "regular", "bonus")
+ ///
+ public required string Code { get; set; }
+
+ ///
+ /// The earning calculation type: "hourly" or "salary"
+ ///
+ public required string Type { get; set; }
+
+ ///
+ /// Number of hours worked (only used when Type is "hourly")
+ ///
+ public decimal? Hours { get; set; }
+
+ ///
+ /// Hourly rate (only used when Type is "hourly")
+ ///
+ public decimal? Rate { get; set; }
+
+ ///
+ /// Fixed salary amount (only used when Type is "salary")
+ ///
+ public decimal? Amount { get; set; }
+
+ ///
+ /// Whether this earning is subject to taxation
+ ///
+ public required bool IsTaxable { get; set; }
+}
diff --git a/gross-to-net/dotnet/models/PayrollResult.cs b/gross-to-net/dotnet/models/PayrollResult.cs
new file mode 100644
index 0000000..4ebe6e6
--- /dev/null
+++ b/gross-to-net/dotnet/models/PayrollResult.cs
@@ -0,0 +1,27 @@
+namespace GrossToNet.Models;
+
+///
+/// Represents the result of a payroll calculation
+///
+public class PayrollResult
+{
+ ///
+ /// Total gross pay (sum of all earnings)
+ ///
+ public decimal GrossPay { get; set; }
+
+ ///
+ /// Total gross taxable earnings (sum of earnings marked as taxable)
+ ///
+ public decimal GrossTaxableEarnings { get; set; }
+
+ ///
+ /// List of all withholdings (deductions and taxes) applied in order
+ ///
+ public List Withholdings { get; set; } = new();
+
+ ///
+ /// Net pay after all withholdings
+ ///
+ public decimal NetPay { get; set; }
+}
diff --git a/gross-to-net/dotnet/models/Tax.cs b/gross-to-net/dotnet/models/Tax.cs
new file mode 100644
index 0000000..f559b73
--- /dev/null
+++ b/gross-to-net/dotnet/models/Tax.cs
@@ -0,0 +1,34 @@
+namespace GrossToNet.Models;
+
+///
+/// Represents a tax calculation applied to income.
+/// Can be a simple percentage or a capped percentage (with a maximum tax amount).
+///
+public class Tax
+{
+ ///
+ /// Identifier for the tax type (e.g., "federalIncome", "fica")
+ ///
+ public required string Code { get; set; }
+
+ ///
+ /// The tax calculation type: "percentage" or "cappedPercentage"
+ ///
+ public required string Type { get; set; }
+
+ ///
+ /// Processing priority; lower values are processed first.
+ /// Determines order of tax calculation.
+ ///
+ public required int Priority { get; set; }
+
+ ///
+ /// The tax rate as a percentage (0-100)
+ ///
+ public required decimal Value { get; set; }
+
+ ///
+ /// Maximum tax amount (only used when Type is "cappedPercentage")
+ ///
+ public decimal? Cap { get; set; }
+}
diff --git a/gross-to-net/dotnet/models/Withholding.cs b/gross-to-net/dotnet/models/Withholding.cs
new file mode 100644
index 0000000..86ab6e2
--- /dev/null
+++ b/gross-to-net/dotnet/models/Withholding.cs
@@ -0,0 +1,27 @@
+namespace GrossToNet.Models;
+
+///
+/// Represents a single withholding entry (tax or deduction) that was applied
+///
+public class Withholding
+{
+ ///
+ /// Type of withholding: "deduction" or "tax"
+ ///
+ public required string Type { get; set; }
+
+ ///
+ /// Code identifying the specific deduction or tax
+ ///
+ public required string Code { get; set; }
+
+ ///
+ /// Amount actually withheld (may be less than requested if insufficient funds)
+ ///
+ public decimal AmountWithheld { get; set; }
+
+ ///
+ /// Amount that could not be withheld due to insufficient remaining pay
+ ///
+ public decimal Deficit { get; set; }
+}
diff --git a/gross-to-net/dotnet/test-cases.json b/gross-to-net/dotnet/test-cases.json
new file mode 100644
index 0000000..7fb3eaa
--- /dev/null
+++ b/gross-to-net/dotnet/test-cases.json
@@ -0,0 +1,319 @@
+[
+ {
+ "description": "Test 1: Simple hourly worker with no deductions or taxes",
+ "input": {
+ "earnings": [
+ {
+ "code": "regular",
+ "type": "hourly",
+ "hours": 40,
+ "rate": 25.00,
+ "isTaxable": true
+ }
+ ],
+ "deductions": [],
+ "taxes": []
+ },
+ "output": {
+ "grossPay": 1000.00,
+ "grossTaxableEarnings": 1000.00,
+ "withholdings": [],
+ "netPay": 1000.00
+ }
+ },
+ {
+ "description": "Test 2: Simple salary worker with no deductions or taxes",
+ "input": {
+ "earnings": [
+ {
+ "code": "salary",
+ "type": "salary",
+ "amount": 5000.00,
+ "isTaxable": true
+ }
+ ],
+ "deductions": [],
+ "taxes": []
+ },
+ "output": {
+ "grossPay": 5000.00,
+ "grossTaxableEarnings": 5000.00,
+ "withholdings": [],
+ "netPay": 5000.00
+ }
+ },
+ {
+ "description": "Test 3: Mixed taxable and non-taxable hourly earnings",
+ "input": {
+ "earnings": [
+ {
+ "code": "regular",
+ "type": "hourly",
+ "hours": 40,
+ "rate": 20.00,
+ "isTaxable": true
+ },
+ {
+ "code": "reimbursement",
+ "type": "hourly",
+ "hours": 10,
+ "rate": 15.00,
+ "isTaxable": false
+ }
+ ],
+ "deductions": [],
+ "taxes": []
+ },
+ "output": {
+ "grossPay": 950.00,
+ "grossTaxableEarnings": 800.00,
+ "withholdings": [],
+ "netPay": 950.00
+ }
+ },
+ {
+ "description": "Test 4: Mixed taxable and non-taxable salary earnings",
+ "input": {
+ "earnings": [
+ {
+ "code": "salary",
+ "type": "salary",
+ "amount": 3000.00,
+ "isTaxable": true
+ },
+ {
+ "code": "expense_reimbursement",
+ "type": "salary",
+ "amount": 500.00,
+ "isTaxable": false
+ }
+ ],
+ "deductions": [],
+ "taxes": []
+ },
+ "output": {
+ "grossPay": 3500.00,
+ "grossTaxableEarnings": 3000.00,
+ "withholdings": [],
+ "netPay": 3500.00
+ }
+ },
+ {
+ "description": "Test 5: Hourly worker with flat pre-tax deduction",
+ "input": {
+ "earnings": [
+ {
+ "code": "regular",
+ "type": "hourly",
+ "hours": 40,
+ "rate": 25.00,
+ "isTaxable": true
+ }
+ ],
+ "deductions": [
+ {
+ "code": "healthInsurance",
+ "type": "flat",
+ "priority": 1,
+ "value": 150.00,
+ "isPreTax": true
+ }
+ ],
+ "taxes": []
+ },
+ "output": {
+ "grossPay": 1000.00,
+ "grossTaxableEarnings": 1000.00,
+ "withholdings": [
+ {
+ "type": "deduction",
+ "code": "healthInsurance",
+ "amountWithheld": 150.00,
+ "deficit": 0.00
+ }
+ ],
+ "netPay": 850.00
+ }
+ },
+ {
+ "description": "Test 6: Salary worker with percentage pre-tax deduction",
+ "input": {
+ "earnings": [
+ {
+ "code": "salary",
+ "type": "salary",
+ "amount": 5000.00,
+ "isTaxable": true
+ }
+ ],
+ "deductions": [
+ {
+ "code": "401k",
+ "type": "percentage",
+ "priority": 1,
+ "value": 10,
+ "isPreTax": true
+ }
+ ],
+ "taxes": []
+ },
+ "output": {
+ "grossPay": 5000.00,
+ "grossTaxableEarnings": 5000.00,
+ "withholdings": [
+ {
+ "type": "deduction",
+ "code": "401k",
+ "amountWithheld": 500.00,
+ "deficit": 0.00
+ }
+ ],
+ "netPay": 4500.00
+ }
+ },
+ {
+ "description": "Test 7: Hourly worker with flat post-tax deduction",
+ "input": {
+ "earnings": [
+ {
+ "code": "regular",
+ "type": "hourly",
+ "hours": 40,
+ "rate": 25.00,
+ "isTaxable": true
+ }
+ ],
+ "deductions": [
+ {
+ "code": "garnishment",
+ "type": "flat",
+ "priority": 1,
+ "value": 200.00,
+ "isPreTax": false
+ }
+ ],
+ "taxes": []
+ },
+ "output": {
+ "grossPay": 1000.00,
+ "grossTaxableEarnings": 1000.00,
+ "withholdings": [
+ {
+ "type": "deduction",
+ "code": "garnishment",
+ "amountWithheld": 200.00,
+ "deficit": 0.00
+ }
+ ],
+ "netPay": 800.00
+ }
+ },
+ {
+ "description": "Test 8: Salary worker with percentage post-tax deduction",
+ "input": {
+ "earnings": [
+ {
+ "code": "salary",
+ "type": "salary",
+ "amount": 4000.00,
+ "isTaxable": true
+ }
+ ],
+ "deductions": [
+ {
+ "code": "charitableDonation",
+ "type": "percentage",
+ "priority": 1,
+ "value": 5,
+ "isPreTax": false
+ }
+ ],
+ "taxes": []
+ },
+ "output": {
+ "grossPay": 4000.00,
+ "grossTaxableEarnings": 4000.00,
+ "withholdings": [
+ {
+ "type": "deduction",
+ "code": "charitableDonation",
+ "amountWithheld": 200.00,
+ "deficit": 0.00
+ }
+ ],
+ "netPay": 3800.00
+ }
+ },
+ {
+ "description": "Test 9: Hourly worker with percentage tax (basic income tax)",
+ "input": {
+ "earnings": [
+ {
+ "code": "regular",
+ "type": "hourly",
+ "hours": 40,
+ "rate": 30.00,
+ "isTaxable": true
+ }
+ ],
+ "deductions": [],
+ "taxes": [
+ {
+ "code": "federalIncome",
+ "type": "percentage",
+ "priority": 1,
+ "value": 15
+ }
+ ]
+ },
+ "output": {
+ "grossPay": 1200.00,
+ "grossTaxableEarnings": 1200.00,
+ "withholdings": [
+ {
+ "type": "tax",
+ "code": "federalIncome",
+ "amountWithheld": 180.00,
+ "deficit": 0.00
+ }
+ ],
+ "netPay": 1020.00
+ }
+ },
+ {
+ "description": "Test 10: Salary worker with capped percentage tax",
+ "input": {
+ "earnings": [
+ {
+ "code": "salary",
+ "type": "salary",
+ "amount": 5000.00,
+ "isTaxable": true
+ }
+ ],
+ "deductions": [],
+ "taxes": [
+ {
+ "code": "socialSecurity",
+ "type": "cappedPercentage",
+ "priority": 1,
+ "value": 6.2,
+ "cap": 250.00
+ }
+ ]
+ },
+ "output": {
+ "grossPay": 5000.00,
+ "grossTaxableEarnings": 5000.00,
+ "withholdings": [
+ {
+ "type": "tax",
+ "code": "socialSecurity",
+ "amountWithheld": 250.00,
+ "deficit": 0.00
+ }
+ ],
+ "netPay": 4750.00
+ }
+ }
+]
\ No newline at end of file
diff --git a/gross-to-net/input.json b/gross-to-net/input.json
deleted file mode 100644
index 71eaa7e..0000000
--- a/gross-to-net/input.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
- "earnings": [
- {
- "code": "overtime",
- "type": "hourly",
- "hours": 20,
- "rate": 15,
- "isTaxable": true
- },
- {
- "code": "regular",
- "type": "hourly",
- "hours": 20,
- "rate": 13,
- "isTaxable": false
- },
- {
- "code": "bonus",
- "type": "salary",
- "amount": 1500,
- "isTaxable": true
- }
- ],
- "deductions": [
- {
- "code": "401k",
- "type": "percentage",
- "priority": 1,
- "value": 10,
- "isPreTax": false
- },
- {
- "code": "healthInsurance",
- "type": "flat",
- "priority": 2,
- "value": 20,
- "isPreTax": true
- }
- ],
- "taxes": [
- {
- "code": "federalIncome",
- "type": "percentage",
- "priority": 1,
- "value": 10
- },
- {
- "code": "fica",
- "type": "cappedPercentage",
- "priority": 2,
- "value": 10,
- "cap": 50
- }
- ]
-}
\ No newline at end of file
diff --git a/gross-to-net/test-cases.json b/gross-to-net/test-cases.json
deleted file mode 100644
index 4e04e20..0000000
--- a/gross-to-net/test-cases.json
+++ /dev/null
@@ -1,166 +0,0 @@
-[
- {
- "input": {
- "earnings": [
- {
- "code": "overtime",
- "type": "hourly",
- "hours": 20,
- "rate": 15,
- "isTaxable": true
- },
- {
- "code": "regular",
- "type": "hourly",
- "hours": 20,
- "rate": 13,
- "isTaxable": false
- },
- {
- "code": "bonus",
- "type": "salary",
- "amount": 1500,
- "isTaxable": true
- }
- ],
- "deductions": [
- {
- "code": "401k",
- "type": "percentage",
- "priority": 1,
- "value": 10,
- "isPreTax": false
- },
- {
- "code": "healthInsurance",
- "type": "flat",
- "priority": 2,
- "value": 20,
- "isPreTax": true
- }
- ],
- "taxes": [
- {
- "code": "federalIncome",
- "type": "percentage",
- "priority": 1,
- "value": 10
- },
- {
- "code": "fica",
- "type": "cappedPercentage",
- "priority": 2,
- "value": 10,
- "cap": 50
- }
- ]
- },
- "output": {
- "GrossPay": 2060,
- "GrossTaxableEarnings": 1800,
- "Withholdings": [
- {
- "Type": "deduction",
- "Code": "healthInsurance",
- "AmountWithheld": 20,
- "Deficit": 0
- },
- {
- "Type": "tax",
- "Code": "fica",
- "AmountWithheld": 50,
- "Deficit": 0
- },
- {
- "Type": "tax",
- "Code": "federalIncome",
- "AmountWithheld": 178,
- "Deficit": 0
- },
- {
- "Type": "deduction",
- "Code": "401k",
- "AmountWithheld": 155.2,
- "Deficit": 0.0
- }
- ],
- "NetPay": 1656.8
- }
- },
- {
- "input": {
- "earnings": [
- {
- "code": "overtime",
- "type": "hourly",
- "hours": 5,
- "rate": 12,
- "isTaxable": true
- },
- {
- "code": "bonus",
- "type": "salary",
- "amount": 115,
- "isTaxable": false
- },
- {
- "code": "regular",
- "type": "hourly",
- "hours": 10,
- "rate": 13,
- "isTaxable": true
- }
- ],
- "deductions": [
- {
- "code": "healthInsurance",
- "type": "percentage",
- "priority": 1,
- "value": 20,
- "isPreTax": true
- },
- {
- "code": "uniform",
- "type": "flat",
- "priority": 1,
- "value": 400,
- "isPreTax": false
- }
- ],
- "taxes": [
- {
- "code": "federalIncome",
- "type": "cappedPercentage",
- "priority": 1,
- "value": 7.5,
- "cap": 250
- }
- ]
- },
- "output": {
- "GrossPay": 305,
- "GrossTaxableEarnings": 190,
- "Withholdings": [
- {
- "Type": "deduction",
- "Code": "healthInsurance",
- "AmountWithheld": 38,
- "Deficit": 0
- },
- {
- "Type": "tax",
- "Code": "federalIncome",
- "AmountWithheld": 11.4,
- "Deficit": 0.0
- },
- {
- "Type": "deduction",
- "Code": "uniform",
- "AmountWithheld": 140.6,
- "Deficit": 259.4
- }
- ],
- "NetPay": 115.0
- }
- }
-]
\ No newline at end of file