Coverage for functions \ flipdare \ constants.py: 100%
0 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
1#!/usr/bin/env python
2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved.
3#
4# This file is part of Flipdare's proprietary software and contains
5# confidential and copyrighted material. Unauthorised copying,
6# modification, distribution, or use of this file is strictly
7# prohibited without prior written permission from Flipdare Pty Ltd.
8#
9# This software includes third-party components licensed under MIT,
10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details.
11#
13# pragma: no cover
16import sys
17from pathlib import Path
18from typing import Any, Final
20from flipdare.app_log import LOG
21from flipdare.generated.shared.model.core.stopwatch_duration import StopwatchDuration
22from flipdare.generated.shared.stripe.stripe_country_code import StripeCountryCode
23from flipdare.generated.shared.stripe.stripe_currency_code import StripeCurrencyCode
26from flipdare.payments.payment_types import StripeRegionFee
28__all__ = [
29 "IS_DEBUG",
30 "IS_TRACE",
31 "NO_COLLECTION",
32 "ADMIN_DOC_ID",
33 "DEF_OK_MESSAGE",
34 "API_TOKEN_PREFIX",
35 "BEARER_TOKEN_PREFIX",
36 "EMAIL_LOG_SUBJECT_MAX_LENGTH",
37 "EMAIL_MAX_LINE_LENGTH",
38 "EMAIL_STACK_LINE_MAX_LENGTH",
39 "TIMESTAMP_KEY_SUFFIX",
40 "MAX_SUSPICION_SCORE",
41 "REMOTE_CONFIG_KEYS",
42 "COMPANY_SHORT_NAME",
43 "COMPANY_SHORT_LINK",
44 "COMPANY_LINK",
45 "COMPANY_TERMS",
46 "COMPANY_REFUND",
47 "COMPANY_LOG_MED",
48 "COMPANY_LONG",
49 "COMPANY_TAGLINE",
50 "COMPANY_ADDRESS",
51 "APP_VERSION",
52 "FFMPEG_COMMAND",
53 "NO_DOC_ID",
54 "DEF_TZ_STRING",
55 "SLUG_CODE_LENGTH",
56 "MAX_ERROR_STRING_LENGTH",
57 "NOT_APPLICABLE",
58 "DELETED_USER_MIN_ID_LENGTH",
59 "DELETED_USER_NAME_PREFIX",
60 "DELETED_USER_EMAIL_DOMAIN",
61 "DELETED_USER_PREFIX",
62 "DEFAULT_USER_AVATAR_PATH",
63 "DEFAULT_GROUP_AVATAR_PATH",
64 "EMAIL_TEMPLATE_DIR",
65 "ADMIN_TEMPLATE_SUB_DIR",
66 "USER_TEMPLATE_SUB_DIR",
67 "MAX_SUMMARY_EMAIL_ENTRIES",
68 "MAX_SUMMARY_EMAIL_ROW_ENTRIES",
69 "INTERNAL_ADMIN_EMAIL",
70 "DEF_NO_REPLY_EMAIL",
71 "DEF_NO_REPLY_LABEL",
72 "DEF_MAIL_FROM",
73 "DEF_MAIL_FROM_LABEL",
74 "DEF_MAIL_SUPPORT",
75 "DEF_MAIL_SUPPORT_LABEL",
76 "MAIL_UNSUBSCRIBE",
77 "MAIL_UNSUBSCRIBE_NAME",
78 "FIREBASE_TMP_RECEIPT_PATH",
79 "FIREBASE_TMP_EMAIL_PATH",
80 "FIREBASE_PUBLIC_EMAIL_TMPL",
81 "FIREBASE_RECEIPT_RETENTION_DAYS",
82 "FIREBASE_EMAIL_RETENTION_DAYS",
83 "CURRENCY_API_TIMEOUT",
84 "BASE_APP_BACKEND_LINK",
85 "IOS_STORE_URL",
86 "ANDROID_STORE_URL",
87 "DEEP_LINK_BASE",
88 "DEFERRED_DEEP_LINK_URL",
89 "FALLBACK_WEB_URL",
90 "BACKEND_BASE_LINK",
91 "REPORT_JOB_STATS_DAYS",
92 "RPORT_LOG_STATS_DAYS",
93 "REPORT_CHARGE_STATS_DAYS",
94 "REPORT_ERROR_STATS_DAYS",
95 "JOB_WAIT_SANITY_SECONDS",
96 "EMAIL_ERROR_COUNT",
97 "MIN_ANALYSIS_COUNT",
98 "DEF_ERROR_SCORE",
99 "SMTP_STATUS_OK",
100 "FIRESTORE_DOC_ID_LENGTH",
101 "STATS_Z_SCORE_THRESHOLD",
102 "STATS_IQR_MULTIPLIER",
103 "EXCHANGE_RATE_URL",
104 "EXCHANGE_RATE_HEADERS",
105 "EXCHANGE_RATE_BASE_CURRENCY",
106 "DEFAULT_CHANGE_SCORE",
107 "TRIGGER_CHANGE_SCORE_THRESHOLD",
108 "HIGH_TRANSACTION_AMOUNT",
109 "HIGH_RISK_SCORE",
110 "MEDIUM_RISK_SCORE",
111 "MEDIUM_LOW_RISK_SCORE",
112 "RISK_ANALYSIS_CHARGE_BACK_GRACE_CT",
113 "DEF_RETRIEVAL_WINDOW_HOURS",
114 "RESTRICTION_MAX_WAIT_ADMIN_ACK_FLAG",
115 "RESTRICTION_REMINDER_USER_RESPONSE_FLAG",
116 "RESTRICTION_MAX_WAIT_USER_RESPONSE_FLAG",
117 "CHAT_REVIEW_REPUTATION_THRESHOLD",
118 "CHAT_REJECT_REPUTATION_THRESHOLD",
119 "DEF_REPUTATION_CACHE_SIZE",
120 "DEF_REPUTATION",
121 "REP_DECAY_EQUILLIBRIUM",
122 "REP_HIGH_RISK_THRESHOLD",
123 "REP_LOW_RISK_THRESHOLD",
124 "REP_REVIEW_PENALTY",
125 "REP_REVIEW_THRESHOLD",
126 "REP_VIOLATION_PENALTY",
127 "REP_CLEAN_REWARD",
128 "REP_DECAY_RATE",
129 "REP_PENALTY_GROWTH_RATE",
130 "MOD_CONFIDENCE_THRESHOLD",
131 "MOD_SHARPNESS",
132 "MOD_MIN_AGREEMENT_CATEGORIES",
133 "MOD_WEIGHTED_FLAGGED_SCORE",
134 "MOD_WEIGHTED_REVIEW_SCORE",
135 "APPROX_INFINITE",
136 "MAX_SEARCH_TAG_COUNT",
137 "MAX_SEARCH_RESULTS",
138 "MAX_SEARCH_RESULTS_PER_PAGE",
139 "TYPESENSE_HARD_PER_PAGE_LIMIT",
140 "MAX_SEARCH_FRIENDS_JOIN",
141 "MAX_SEARCH_GROUP_MEMBERS",
142 "SEARCH_CACHE_TTL_SECONDS",
143 "SEARCH_CACHE_MAX_SIZE",
144 "MIN_SEARCH_TYPOS",
145 "MIN_SEARCH_AUTOCOMPLETE_TYPOS",
146 "MAX_SEARCH_AUTOCOMPLETE_RESULTS_PER_PAGE",
147 "MAX_SEARCH_AUTOCOMPLETE_RESULTS",
148 "MAX_DB_CURSOR_LIMIT",
149 "RUNTIME_DB_REFRESH_INTERVAL",
150 "DEF_SIGNED_URL_EXPIRATION",
151 "VOTING_DURATION",
152 "PAYMENT_PERIOD_HRS",
153 "GRAPH_LOG_SCALE_THRESHOLD",
154 "THOMPSON_SAMPLING_ITERATIONS",
155 "THOMPSON_THRESHOLD",
156 "THOMPSON_CONFIDENCE",
157 "KEMENY_MIN_VOTES",
158 "KEMENY_MIN_PERCENT_PENDING",
159 "KEMENY_APPROVAL_THRESHOLD",
160 "KEMENY_REJECTION_THRESHOLD",
161 "KEMENY_PENDING_WEIGHT",
162 "WILSON_CONFIDENCE_INTERVAL",
163 "SPRT_FALSE_POSITIVE_RATE",
164 "SPRT_FALSE_NEGATIVE_RATE",
165 "SPRT_MIN_VOTES",
166 "SPRT_SHOULD_REJECT_PROBABILITY",
167 "SPRT_SHOULD_APPROVE_PROBABILITY",
168 "BAYESIAN_PRIOR_ALPHA",
169 "BAYESIAN_PRIOR_BETA",
170 "BAYESIAN_MAJORITY_THRESHOLD",
171 "BAYESIAN_MINORITY_THRESHOLD",
172 "BAYESIAN_CDF_THRESHOLD",
173 "STRIPE_SUPPORT_EMAIL",
174 "STRIPE_COMPANY_LINK",
175 "STRIPE_DISPLAY_NAME_TMPL",
176 "STRIPE_DOING_BUSINESS_AS_TMPL",
177 "STRIPE_PRODUCT_DESC_TMPL",
178 "STRIPE_DEFAULT_MCC",
179 "STRIPE_DISPUTE_URL",
180 "STRIPE_DASHBOARD_URL",
181 "STRIPE_CLIENT_NETWORK_RETRIES",
182 "STRIPE_VERSION",
183 "STRIPE_STATEMENT_DESCRIPTOR",
184 "STRIPE_STATEMENT_PREFIX",
185 "STRIPE_FALLBACK_PREFIX_STR",
186 "STRIPE_FALLBACK_LAST_STR",
187 "DEFAULT_REAUTHORIZATION_DAYS_CUSTOMER",
188 "STRIPE_MAX_STATEMENT_LENGTH",
189 "STRIPE_STATEMENT_EXCLUSION_CHARS",
190 "STRIPE_INVOICE_PREFIX_LENGTH",
191 "STRIPE_BASE_FEES",
192 "STRIPE_FEE_MAPPING",
193 "DEF_CURRENCY_CODE",
194 "DEF_COUNTRY_CODE",
195 "PAYMENT_MAX_RETRIES",
196 "PAYMENT_MAX_INFO_RETRIES",
197]
200# misc, need here to avoid circular imports
201def _get_system_root_for(sub_directory: str | Path) -> Path:
202 if sys.platform == "win32":
203 # Returns 'C:\' or 'D:\' based on where the script is running
204 root = Path().cwd().resolve().anchor
205 return Path(root) / sub_directory
206 else:
207 # On Unix, Path("/") is the explicit absolute root
208 return Path("/").resolve() / sub_directory
211IS_DEBUG = LOG().is_debug
212IS_TRACE = LOG().is_trace or LOG().is_debug
214NO_COLLECTION = "NoCollection"
215ADMIN_DOC_ID = "__ADMIN_ADMIN__ADMIN_ADMIN__" # needs to be 28 chars
217DEF_OK_MESSAGE = "Ok"
219API_TOKEN_PREFIX = "Basic" # noqa: S105
220BEARER_TOKEN_PREFIX = "Bearer" # noqa: S105
222EMAIL_LOG_SUBJECT_MAX_LENGTH = 40
223EMAIL_MAX_LINE_LENGTH = 500
225EMAIL_STACK_LINE_MAX_LENGTH = 500
227TIMESTAMP_KEY_SUFFIX = ["_at", "At"]
229# -----------------------------------------------------------------------------
230# forms
231# -----------------------------------------------------------------------------
233# this is the field name we expect for the honeypot in our requests, can be changed as needed
234HONEYPOT_NAME: Final = "address"
235MAX_SUSPICION_SCORE = 2 # the max score a request can get before we consider it suspicious
237# -----------------------------------------------------------------------------
238# app
239# -----------------------------------------------------------------------------
241REMOTE_CONFIG_KEYS = ["job_config"]
243COMPANY_SHORT_NAME = "flipdare"
244COMPANY_SUPPORT_URL = "https://flipdare.com/support"
246COMPANY_SHORT_LINK = "flipdare.com"
247COMPANY_LINK = f"https://{COMPANY_SHORT_LINK}"
248COMPANY_TERMS = f"https://{COMPANY_SHORT_LINK}/terms"
249COMPANY_REFUND = f"https://{COMPANY_SHORT_LINK}/refunds"
251# RELEASE: update hosting for the logo.
252COMPANY_LOG_MED = Path(__file__).parent / "config" / "logo" / "logo-512x512.png"
254# RELEASE: update address to US LLC address for tax purposes.
255COMPANY_LONG = "Flipdare Inc."
256COMPANY_TAGLINE = "flipdare.com - You game?"
259COMPANY_ADDRESS = f"""{COMPANY_LONG}
2601234, Business Rd
261Sydney, NSW
2622000, Australia"""
265APP_VERSION = "1.0.0"
266FFMPEG_COMMAND = "ffmpeg"
267NO_DOC_ID = "NoDocID"
268DEF_TZ_STRING = "UTC"
270SLUG_CODE_LENGTH = 14
272MAX_ERROR_STRING_LENGTH = 150
273NOT_APPLICABLE = "N/A"
275DELETED_USER_MIN_ID_LENGTH = 8
276DELETED_USER_NAME_PREFIX = "Former"
277DELETED_USER_EMAIL_DOMAIN = "deleted.flipdare.com"
278DELETED_USER_PREFIX = "N/A"
280DEFAULT_USER_AVATAR_PATH = "content/user/"
281DEFAULT_GROUP_AVATAR_PATH = "content/group/"
283# paths
284JOB_CONFIG_PATH: Path = Path(__file__).parent / "config" / "job_config.yaml"
285DEFAULT_AVATAR_CONFIG_PATH: Path = Path(__file__).parent / "config" / "default_avatar.yaml"
287DOWNLOAD_FILE_DIR: Path = _get_system_root_for("tmp")
289EMAIL_TEMPLATE_DIR = Path(__file__).parent / "config" / "email_template"
290ADMIN_TEMPLATE_SUB_DIR = "admin"
291USER_TEMPLATE_SUB_DIR = "user"
293# email
294MAX_SUMMARY_EMAIL_ENTRIES = 12
295MAX_SUMMARY_EMAIL_ROW_ENTRIES = 6
297INTERNAL_ADMIN_EMAIL = "team@flipdare.com"
299DEF_NO_REPLY_EMAIL = "no-reply@flipdare.com"
300DEF_NO_REPLY_LABEL = "team flipdare"
302DEF_MAIL_FROM = "admin@flipdare.com"
303DEF_MAIL_FROM_LABEL = "flipdare admin"
305DEF_MAIL_SUPPORT = "support@flipdare.com"
306DEF_MAIL_SUPPORT_LABEL = "Cartman from flipdare"
308MAIL_UNSUBSCRIBE = DEF_MAIL_SUPPORT
309MAIL_UNSUBSCRIBE_NAME = DEF_MAIL_SUPPORT_LABEL
311# these are temp public paths for files that
312# are cleaned up after a certain amount of time
313FIREBASE_TMP_RECEIPT_PATH = "public-receipts"
314FIREBASE_TMP_EMAIL_PATH = "public_email"
316FIREBASE_PUBLIC_EMAIL_TMPL = (
317 "https://firebasestorage.googleapis.com/v0/b/$BUCKET_NAME/o/$SAFE_PATH?alt=media&token=$TOKEN"
318)
320FIREBASE_RECEIPT_RETENTION_DAYS = 90
321FIREBASE_EMAIL_RETENTION_DAYS = 180
323# timeouts
324CURRENCY_API_TIMEOUT = 60 # seconds
326# -----------------------------------------------------------------------------
327# links/deep links
328# -----------------------------------------------------------------------------
329BASE_APP_BACKEND_LINK = "https://localhost:8000/"
331IOS_STORE_URL = "https://apps.apple.com/app/idYOUR_APP_ID"
332ANDROID_STORE_URL = "https://play.google.com/store/apps/details?id=com.flipdare.app"
334DEEP_LINK_BASE = "https://flipdare.com/l"
335DEFERRED_DEEP_LINK_URL = "https://chottu.link"
337FALLBACK_WEB_URL = "https://flipdare.com"
340# this is for AppBackendLink, which creates
341# backend links for admin/dashboard use...
342# NOTE: this needs to be set to LOCALHOST for the time being.
343BACKEND_BASE_LINK = "https://localhost:8000/"
345# -----------------------------------------------------------------------------
346# reporting/jobs
347# -----------------------------------------------------------------------------
349REPORT_JOB_STATS_DAYS = 1
350RPORT_LOG_STATS_DAYS = 1
351REPORT_CHARGE_STATS_DAYS = 3
352REPORT_ERROR_STATS_DAYS = 7
354# for jobs
355JOB_WAIT_SANITY_SECONDS = 15 * 60 # 15 minutes
356EMAIL_ERROR_COUNT = 10
358# for reporting
359MIN_ANALYSIS_COUNT = 2
360DEF_ERROR_SCORE = 1.0
362# misc
363SMTP_STATUS_OK = 250
365FIRESTORE_DOC_ID_LENGTH = 128
367STATS_Z_SCORE_THRESHOLD = 2
368STATS_IQR_MULTIPLIER = 1.5
370# -----------------------------------------------------------------------------
371# exchange rates
372# -----------------------------------------------------------------------------
373EXCHANGE_RATE_URL = "https://openexchangerates.org/api/latest.json?app_id=__API_KEY__&base=USD&prettyprint=false&show_alternative=false"
375EXCHANGE_RATE_HEADERS = {"accept": "application/json"}
377EXCHANGE_RATE_BASE_CURRENCY = StripeCurrencyCode.USD
379# -----------------------------------------------------------------------------
380# stats
381# -----------------------------------------------------------------------------
383# NOTE: the lower the sensitivity, the more likely a change will trigger a
384# NOTE: return a change score close to 1.0,
385# NOTE: which would trigger a job to update the model in the database.
386# Configuration: (sensitivity, is_currency)
387# Lower sensitivity = High Priority (triggers faster)
388DEFAULT_CHANGE_SCORE = 1.0
389TRIGGER_CHANGE_SCORE_THRESHOLD = 0.5
391LOW_PRI_SENSITIVITY: float = 2.0
392MED_PRI_SENSITIVITY: float = 1.0
393HIGH_PRI_SENSITIVITY: float = 0.1
395# Cents changes need to be much larger to trigger than count changes
396CENTS_PRIORITY_BUFFER: float = 40.0
398STATS_SENSITIVITY: dict[str, dict[str, tuple[float, bool]]] = {
399 "view_stats": {
400 "bookmarks": (HIGH_PRI_SENSITIVITY, False), # High priority count
401 "flags": (LOW_PRI_SENSITIVITY, False), # Low priority count
402 "likes": (HIGH_PRI_SENSITIVITY, False), # High priority count
403 "dislikes": (MED_PRI_SENSITIVITY, False), # Medium priority count
404 "views": (LOW_PRI_SENSITIVITY, False), # Low priority count
405 },
406 "dare_stats": {
407 "backers": (HIGH_PRI_SENSITIVITY, False), # High priority count
408 "flags": (LOW_PRI_SENSITIVITY, False), # Low priority count
409 "requested": (MED_PRI_SENSITIVITY, False), # Medium priority count
410 "accepted": (HIGH_PRI_SENSITIVITY, False), # High priority count
411 "completed": (HIGH_PRI_SENSITIVITY, False), # High priority count
412 "earnings": (MED_PRI_SENSITIVITY, True), # Currency (less sensitive)
413 },
414 "pledge_stats": {
415 # NOTE:, since cents can be large numbers, we want to use a lower sensitivity for
416 # NOTE: changes in cents, and a higher sensitivity for changes in count
417 "earned.count": (HIGH_PRI_SENSITIVITY, False), # High priority count
418 "earned.cents": (MED_PRI_SENSITIVITY, True), # Currency (less sensitive)
419 "accepted.count": (HIGH_PRI_SENSITIVITY, False), # High priority count
420 "accepted.cents": (MED_PRI_SENSITIVITY, True), # Currency (less sensitive)
421 "pending.count": (MED_PRI_SENSITIVITY, False), # Medium priority count
422 "pending.cents": (LOW_PRI_SENSITIVITY, True), # Currency (less sensitive)
423 "disputed.count": (LOW_PRI_SENSITIVITY, False), # Low priority count
424 "disputed.cents": (LOW_PRI_SENSITIVITY, True), # Currency (less sensitive)
425 "refunded.count": (LOW_PRI_SENSITIVITY, False), # Low priority count
426 "refunded.cents": (LOW_PRI_SENSITIVITY, True), # Currency (less sensitive)
427 "rejected.count": (LOW_PRI_SENSITIVITY, False), # Low priority count
428 "rejected.cents": (LOW_PRI_SENSITIVITY, True), # Currency (less sensitive)
429 "cancelled.count": (LOW_PRI_SENSITIVITY, False), # Low priority count
430 "cancelled.cents": (LOW_PRI_SENSITIVITY, True), # Currency (less sensitive)
431 "error.count": (LOW_PRI_SENSITIVITY, False), # Low priority count
432 "error.cents": (LOW_PRI_SENSITIVITY, True), # Currency (less sensitive)
433 },
434}
436# -----------------------------------------------------------------------------
437# RISK
438# -----------------------------------------------------------------------------
440# $250.00 in cents, transactions above this amount are considered high risk
441HIGH_TRANSACTION_AMOUNT = 250_00
442HIGH_RISK_SCORE = 85
443MEDIUM_RISK_SCORE = 50
444MEDIUM_LOW_RISK_SCORE = 30
446# -----------------------------------------------------------------------------
447# risk analysis
448# -----------------------------------------------------------------------------
450RISK_ANALYSIS_CHARGE_BACK_GRACE_CT = 2
452# -----------------------------------------------------------------------------
453# safety
454# -----------------------------------------------------------------------------
456# note, use 2 hours to account for any time sync issues
457# note, since this should be run every hour, 4 hours is safe
458DEF_RETRIEVAL_WINDOW_HOURS = 4
459RESTRICTION_MAX_WAIT_ADMIN_ACK_FLAG = 3 # days
461RESTRICTION_REMINDER_USER_RESPONSE_FLAG = 3 # days
462RESTRICTION_MAX_WAIT_USER_RESPONSE_FLAG = 7 # days
464CHAT_REVIEW_REPUTATION_THRESHOLD = -20
465CHAT_REJECT_REPUTATION_THRESHOLD = 0
467DEF_REPUTATION_CACHE_SIZE = 10000
468DEF_REPUTATION = 50
470REP_DECAY_EQUILLIBRIUM = 50
471REP_HIGH_RISK_THRESHOLD = 30
472REP_LOW_RISK_THRESHOLD = 70
473REP_REVIEW_PENALTY = -5
474REP_REVIEW_THRESHOLD = 20
475REP_VIOLATION_PENALTY = -20
476REP_CLEAN_REWARD = 1
477REP_DECAY_RATE = 0.9
478REP_PENALTY_GROWTH_RATE = 0.1
480REP_DEFAULT_MULTIPLIERS: dict[str, float] = {
481 "text": 1.0,
482 "image": 1.5, # Images slightly higher risk
483 "video": 1.2,
484 "profile": 2.0, # Profile content analyzed more
485}
488MOD_CONFIDENCE_THRESHOLD = 0.7
489MOD_SHARPNESS = 2.5
490MOD_MIN_AGREEMENT_CATEGORIES = 2
491MOD_WEIGHTED_FLAGGED_SCORE = 0.35
492MOD_WEIGHTED_REVIEW_SCORE = 0.15
495APPROX_INFINITE = 1e-10
497# -----------------------------------------------------------------------------
498# search
499# -----------------------------------------------------------------------------
502# this is used in tag generation to limit the amount of tags
503# generated for groups..
504MAX_SEARCH_TAG_COUNT = 10000
506# the max number of results to return for any query.
507# typesense is optimized for 250, but we
508# allow +250 in case the user wants more!
509MAX_SEARCH_RESULTS = 500
511# per page, dont want to high because its a bit of processing...
512MAX_SEARCH_RESULTS_PER_PAGE = 50
514# this is only required for friend unions.
515TYPESENSE_HARD_PER_PAGE_LIMIT = 250 # Typesense hard limit
516# this is only required for friend unions.
517MAX_SEARCH_FRIENDS_JOIN = 1000
519# this limits the number of friend relationships we create for group members.
520MAX_SEARCH_GROUP_MEMBERS = 500
522# Default cache settings: 5 min TTL, max 1000 entries
523SEARCH_CACHE_TTL_SECONDS = 300
524SEARCH_CACHE_MAX_SIZE = 1000
526MIN_SEARCH_TYPOS = 1
527MIN_SEARCH_AUTOCOMPLETE_TYPOS = 2
529MAX_SEARCH_AUTOCOMPLETE_RESULTS_PER_PAGE = 15
530MAX_SEARCH_AUTOCOMPLETE_RESULTS = 50
532# database
533MAX_DB_CURSOR_LIMIT = 1000
535RUNTIME_DB_REFRESH_INTERVAL = 5 * 60 # seconds (5 minutes)
536DEF_SIGNED_URL_EXPIRATION = 7 * 24 * 60 * 60 # 7 days
539SEARCH_NAT_LANG_CONFIG: dict[str, Any] = {
540 "id": "gemini-flash",
541 "model_name": "google/gemini-2.5-flash",
542 "api_key": "__API_KEY__",
543 "max_bytes": 16000,
544 "temperature": 0.0,
545 "system_prompt": "Optional custom system prompt to append to the one that Typesense generates based on your dataset",
546}
549# -----------------------------------------------------------------------------
550# VOTING
551# -----------------------------------------------------------------------------
554VOTING_DURATION = StopwatchDuration.THREE_DAYS
555PAYMENT_PERIOD_HRS = 168 # hours / 7 days
557#
558# plotting
559#
560GRAPH_LOG_SCALE_THRESHOLD = 2500.0
561#
562# Thompson Sampling parameters
563#
564THOMPSON_SAMPLING_ITERATIONS = 10000
565THOMPSON_THRESHOLD = 0.5
566THOMPSON_CONFIDENCE = 0.90
567#
568# Kemeny-Young parameters
569#
570KEMENY_MIN_VOTES = 3
571KEMENY_MIN_PERCENT_PENDING = 0.25
572KEMENY_APPROVAL_THRESHOLD = 0.67
573KEMENY_REJECTION_THRESHOLD = 0.33
574KEMENY_PENDING_WEIGHT = 0.45
575#
576# Wilson Score Interval parameters
577#
578WILSON_CONFIDENCE_INTERVAL = 0.95
580#
581# Sequential Probability Ratio Test (SPRT) parameters
582#
583# 5% chance of wrong approval
584SPRT_FALSE_POSITIVE_RATE = 0.05
585# 5% chance of wrong rejection
586SPRT_FALSE_NEGATIVE_RATE = 0.05
587# Minumum votes required for SPRT to make a decision
588SPRT_MIN_VOTES = 5
589# Hypotheses: p0 = 0.4 (should reject), p1 = 0.6 (should approve)
590SPRT_SHOULD_REJECT_PROBABILITY = 0.4
591SPRT_SHOULD_APPROVE_PROBABILITY = 0.6
594#
595# Bayesian Average parameters
596#
597BAYESIAN_PRIOR_ALPHA = 1.0
598BAYESIAN_PRIOR_BETA = 1.0
599# Very confident it will pass
600BAYESIAN_MAJORITY_THRESHOLD = 0.95
601# Very confident it will fail
602BAYESIAN_MINORITY_THRESHOLD = 0.06
603# Probability that approval rate > 0.45
604# NOTE: Using 0.45 threshold to account for pending votes
605BAYESIAN_CDF_THRESHOLD = 0.45
607# -----------------------------------------------------------------------------
608# stripe
609# -----------------------------------------------------------------------------
611STRIPE_SUPPORT_EMAIL = "stripe-support@flipdare.com"
612STRIPE_COMPANY_LINK = COMPANY_LINK
613STRIPE_DISPLAY_NAME_TMPL = "FLIP - $NAME"
614STRIPE_DOING_BUSINESS_AS_TMPL = "$NAME from flipdare"
615STRIPE_PRODUCT_DESC_TMPL = "content by $NAME on flipdare.com"
616STRIPE_DEFAULT_MCC = "4899" # "Cable and Other Pay Television Services"
618STRIPE_DISPUTE_URL = "https://dashboard.stripe.com/disputes/{dispute_id}"
619STRIPE_DASHBOARD_URL = "https://dashboard.stripe.com/login"
621STRIPE_CLIENT_NETWORK_RETRIES = 3
622STRIPE_VERSION = "2026-01-28.clover"
624STRIPE_STATEMENT_DESCRIPTOR = "FLIPDARE" # Your marketplace brand
625STRIPE_STATEMENT_PREFIX = "FLIP" # Shorter version for cards
627STRIPE_FALLBACK_PREFIX_STR = "User"
628STRIPE_FALLBACK_LAST_STR = "X"
630# default reauth period for Customer-Initiated Transaction authorization
631# technically 7 days, but we use 6 to ensure they are reauthorized before the 7 day limit,
632# to avoid any issues with auth expiring before capture.
633DEFAULT_REAUTHORIZATION_DAYS_CUSTOMER = 6
635#
636# Character limits
637# Ref: https://stripe.com/docs/payments/payment-intents
638# statement_descriptor 22
639# Statement descriptors are limited to 22 characters, can't use the special characters
640# <, >, ', ", or *, and must not consist solely of numbers.
641# When using dynamic statement descriptors,
642# the dynamic text is appended to the statement descriptor prefix set in the Stripe Dashboard.
643# An asterisk (*) and an empty space are also added to separate the default statement descriptor
644# from the dynamic portion.
645# These 2 characters count towards the 22 character limit.
646STRIPE_MAX_STATEMENT_LENGTH = 22
647STRIPE_STATEMENT_EXCLUSION_CHARS = {"<", ">", "'", '"', "*"}
649# The prefix for the customer used to generate unique invoice numbers.
650# Must be 3-12 uppercase letters or numbers.
651# NOTE: be careful changing this, as it can cause collisions in invoice numbers
652# NOTE: which would cause charges to fail.
653# NOTE: if changed, you will need to check the db, for the invoice Id stored in stripe settings,
654# NOTE: otherwise you will get conflicts!
655STRIPE_INVOICE_PREFIX_LENGTH = 12
658# FEES
659STRIPE_BASE_FEES = {
660 # Rates: [Percentage, Fixed, Int'l Surcharge, Currency Surcharge]
661 "US": StripeRegionFee(0.029, 0.30, 0.015, 0.01),
662 "EU": StripeRegionFee(0.015, 0.25, 0.0175, 0.02),
663 "UK": StripeRegionFee(0.015, 0.20, 0.0175, 0.02),
664 "AU": StripeRegionFee(0.017, 0.30, 0.018, 0.02),
665 "CA": StripeRegionFee(0.029, 0.30, 0.008, 0.02),
666 "SG": StripeRegionFee(0.034, 0.50, 0.01, 0.02),
667 "JP": StripeRegionFee(0.036, 0.00, 0.00, 0.02),
668}
670STRIPE_FEE_MAPPING = {
671 StripeCurrencyCode.USD: "US",
672 StripeCurrencyCode.AUD: "AU",
673 StripeCurrencyCode.CAD: "CA",
674 StripeCurrencyCode.GBP: "UK",
675 StripeCurrencyCode.JPY: "JP",
676 StripeCurrencyCode.SGD: "SG",
677 StripeCurrencyCode.EUR: "EU",
678 StripeCurrencyCode.BGN: "EU",
679 StripeCurrencyCode.CZK: "EU",
680 StripeCurrencyCode.DKK: "EU",
681 StripeCurrencyCode.HUF: "EU",
682 StripeCurrencyCode.PLN: "EU",
683 StripeCurrencyCode.RON: "EU",
684 StripeCurrencyCode.SEK: "EU",
685}
687# -----------------------------------------------------------------------------
688# payments
689# -----------------------------------------------------------------------------
691DEF_CURRENCY_CODE = StripeCurrencyCode.USD
692DEF_COUNTRY_CODE = StripeCountryCode.US
694# the number of times we retry processing before stopping.
695PAYMENT_MAX_RETRIES = 10 # for any status.
696PAYMENT_MAX_INFO_RETRIES = 14 # approx 14 days. (for additional info)