Finance is a set of static numeric functions, not a tax or credit decision system. It reuses formulas but has no taxpayer or contract aggregate, policy store, occurrence date, evidence, filing, approval, or result ledger.
1. BracketTaxCalculator
TaxBracket contains UpperLimit, Rate, and QuickDeduction. Despite its name, Auction and LegalCalculators also reuse it. CalcByQuickFormula chooses the first bracket whose upper limit contains the amount and otherwise uses the final bracket. CalcProgressive accumulates each interval.
// Require ascending limits, nonnegative rates, and a final catch-all bracket first.TaxBracket[] brackets =[ new(100_000m, 0.02m, 0m), new(decimal.MaxValue, 0.01m, 0m),];
// The progressive engine accumulates but performs no validation or rounding.var amount = BracketTaxCalculator.CalcProgressive(150_000m, brackets);
// 100000×2% + 50000×1% = 2500.Debug.Assert(amount == 2_500m);An empty schedule or nonpositive amount returns zero rather than Validation. Unordered, duplicate, negative-rate, negative-deduction, or incomplete schedules are accepted.
2. Comprehensive-income tax
The seven thresholds, rates, and quick deductions match the current official comprehensive-income table. Input is annual taxable income or annual income minus basic, social-insurance, and special-additional deductions. Final tax rounds two places AwayFromZero.
The second overload rejects only negative annual income. A negative deduction increases taxable income. There is no resident status, foreign income, other deduction, relief, prepaid tax, annual reconciliation, or TaxYear.
// Model only the three deductions accepted by this convenience overload.var result = IncomeTaxCalculator.CalcIndividualIncomeTax( annualIncome: 300_000m, basicDeduction: 60_000m, socialInsuranceDeduction: 36_000m, specialAdditionalDeduction: 24_000m);
// Success proves only the hard-coded seven-bracket formula ran.if (result.IsFailure) return result.Error;return new TaxEstimate(result.Value, policyVersion, inputEvidenceHash);TaxEstimate is a target wrapper DTO, not a current library type.
3. Labor-service withholding
Income up to 4,000 deducts 800; above 4,000 deducts 20%, followed by 20/30/40% quick-formula brackets. Official guidance also requires resident individuals to include labor remuneration in annual comprehensive-income reconciliation.
The caller must establish transaction/month aggregation, residency, withholding agent, and annual treatment. One decimal cannot represent these facts.
4. Annual bonus
The method divides bonus by 12 to pick a monthly bracket, then applies bonus × rate − quick deduction. The current official announcement allows residents to choose separate taxation or comprehensive-income inclusion through 2027-12-31.
The method has no TaxYear, cannot compare the two choices, and does not enforce one use per taxpayer per year. Without a policy EffectiveTo, it will continue calculating after expiry.
5. Equal-installment mortgage
Monthly rate is annualRate/12 and months is years×12. Decimal exponentiation computes (1+r)^n; zero interest uses principal/months.
The schedule rounds monthly interest and principal, absorbing residual principal in the final period. Total-interest API instead multiplies the already rounded monthly payment by months, so its result can differ by cents from summing schedule interest.
6. Equal-principal mortgage
Monthly principal is rounded two places, with the last period absorbing residual. Interest uses opening principal times monthly rate. The total-interest formula does not round each period and can differ from the schedule sum.
// Fix principal, annual rate, and term; the API has no actual drawdown date.var schedule = MortgageCalculator.CalcEqualPrincipalSchedule( principal: 1_200_000m, annualRate: 0.035m, years: 20);
if (schedule.IsFailure) return schedule.Error;
// A statement uses one per-period rounding rule and derives totals from its rows.var totalInterest = schedule.Value.Sum(item => item.Interest);var totalPrincipal = schedule.Value.Sum(item => item.Principal);Debug.Assert(totalPrincipal == 1_200_000m);There is no drawdown date, actual payment date, daily interest, leap-year rule, repricing, prepayment, fee, penalty interest, or bank-specific convention.
7. Mortgage capacity and overflow
Years has only a lower bound. years * 12 uses int arithmetic and can overflow; large month counts can overflow decimal exponentiation or allocate enormous schedules. Public wrappers need principal, rate, term, and maximum-row limits.
Principal zero produces zero values or an all-zero schedule. Annual rate has no reasonable upper bound, so a very high nonnegative rate can throw decimal overflow rather than return Result Failure.
8. Private-lending rate rule
ValidateInterestRateCeiling delegates to Domain InterestCalculator. GetInterestRateCeiling simply returns LPR×4 with no validation. The judicial interpretation ties the standard to the one-year LPR at contract formation and includes transitional treatment for older contracts.
The API does not accept contract date, filing/acceptance date, contract category, or whether the relationship is private lending. Policy selection must happen first.
9. Segmented interest
Each segment computes principal × annualRate × days / 365 with fixed principal. Negative rates fail; nonpositive days are silently skipped. Date subtraction excludes EndDate despite record comments describing it as inclusive.
InterestSegment[] segments =[ new(new DateTime(2026, 1, 1), new DateTime(2026, 2, 1), 0.04m), new(new DateTime(2026, 2, 1), new DateTime(2026, 3, 1), 0.036m),];
// Executable behavior is [StartDate, EndDate), so the boundary is not doubled.var result = LoanDetailCalculator.CalcInterestBySegments(500_000m, segments);
// The engine does not reject overlap, gaps, or disorder.if (result.IsFailure) return result.Error;Principal repayments during the period are not modeled and can make fixed-principal interest too high.
10. Allocation and compound ceiling
Allocation applies fees, then accrued interest, then principal; excess payment is Remaining. Each output component is rounded after allocation, so the displayed parts can differ slightly from the unrounded arithmetic.
Compound-ceiling validation computes principal×LPR×4×days/365 and compares a caller-supplied compound-interest amount. It does not derive or verify the interest ledger. This is arithmetic evidence, not a court determination.
11. PenaltyCalculator
Agreed, daily, and LPR-reference methods are direct formulas. SuggestAdjustedPenalty caps its suggestion at actualLoss×1.30 and returns zero when loss is zero.
The 2023 Civil Code contract interpretation says a penalty exceeding loss by 30% may generally be found excessive, while requiring assessment of parties, transaction, performance, fault, background, fairness, and good faith. A deterministic “adjusted amount” hides that discretion. A RiskIndicator would be a safer contract.
12. Numeric and error contract
Public APIs mix Result and bare decimal. Stable Finance codes coexist with Chinese descriptions and generic InvalidInput errors. Decimal overflow is not mapped to Result. Wrappers must bound input and provide one exception boundary.
Money has no Currency, rate has no Percent value object, and days have no DayCountConvention. Cross-currency and 360/365/actual conventions cannot be represented safely.
13. Test matrix
| Capability | Required edges |
|---|---|
| Bracket | Every bound ±0.01, disorder, negative rate, missing final bracket |
| Income tax | Seven boundaries, negative deduction, tax year, official golden values |
| Labor | 800/4,000/20,000/50,000 boundaries and annual-reconciliation warning |
| Bonus | Monthly boundaries, separate/included comparison, 2027 expiry |
| Mortgage | Zero rate, 1/30 years, row totals, cent differences, overflow/capacity |
| Segment | Same day, inclusivity, leap year, overlap/gap/order/repayment |
| Allocation | Zero, exact payoff, excess, rounding reconciliation |
| Penalty | Zero loss, 130% boundary, unmodeled discretion factors |
None of these tests currently exists.
14. Inspection commands
# Formula, rounding, date subtraction, and bracket behavior.rg -n "Math.Round|AwayFromZero|TotalDays|CalcByQuickFormula|CalcProgressive|years \* 12" \ src/Platform/IndustryExtensions/BitzOrcas.Platform.IndustryExtensions.Finance -g '*.cs'
# Policy/date/currency/breakdown/test capabilities should have no complete implementation.rg -n "TaxYear|ContractDate|Currency|DayCountConvention|PolicyVersion|\[Fact\]" \ src/Platform/IndustryExtensions tests -g '*.cs'Industry Extensions overview · HR leave policies · Auction and GA