This commit is contained in:
Bastien COIGNOUX
2026-05-03 20:18:33 +02:00
parent ffc2e6b895
commit bd325fe456
113 changed files with 29532 additions and 220 deletions

146
mdb-predator/app/index.tsx Normal file
View File

@ -0,0 +1,146 @@
import { router } from 'expo-router';
import { useMemo, useState } from 'react';
import {
Pressable,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import { runFinancialAgent } from '../src/agents/orchestrator';
import { analyzeDeal, RED_FLAG_MARGIN_PCT, type DealAnalysisInput } from '../src/core/dealAnalysis';
import { sharePurchaseOfferPdf } from '../src/services/offerPdf';
import { colors } from '../src/theme/colors';
const DEMO_PROPERTY_ID = 'local-demo';
export default function RedFlagDashboard() {
const [asking, setAsking] = useState('185000');
const [resale, setResale] = useState('268000');
const [surface, setSurface] = useState('88');
const [works, setWorks] = useState('42000');
const input: DealAnalysisInput = useMemo(
() => ({
resaleEstimateTtc: Number(resale.replace(/\s/g, '')) || 0,
surfaceM2: Number(surface.replace(',', '.')) || 1,
worksTotalEur: Number(works.replace(/\s/g, '')) || 0,
notaryRateOnPurchase: 0.077,
saleAgencyRateOnResale: 0.05,
miscAcquisitionEur: 2500,
miscSaleEur: 1800,
holdingMonths: 6,
holdingAnnualRateOnPrincipal: 0.055,
}),
[resale, surface, works],
);
const analysis = useMemo(() => analyzeDeal(input), [input]);
const askNum = Number(asking.replace(/\s/g, '')) || 0;
const fin = useMemo(
() => runFinancialAgent(input, askNum),
[input, askNum],
);
const red = analysis.isRedFlag(askNum);
return (
<ScrollView contentContainerStyle={styles.scroll}>
<View style={[styles.banner, red ? styles.bannerBad : styles.bannerOk]}>
<Text style={styles.bannerTitle}>{red ? 'RED FLAG' : 'GO'}</Text>
<Text style={styles.bannerSub}>
Marge nette à prix affiché : {(analysis.netMarginPct(askNum) * 100).toFixed(1)} %
{' — '}seuil {(RED_FLAG_MARGIN_PCT * 100).toFixed(0)} %
</Text>
<Text style={styles.bannerSub}>
Prix dachat max (algo) :{' '}
{fin.maxBuyingPriceEur.toLocaleString('fr-FR')}
</Text>
</View>
<Text style={styles.h}>Hypothèses deal</Text>
<Field label="Prix affiché / offre actuelle (€)" value={asking} onChange={setAsking} />
<Field label="Prix de revente estimé TTC (€)" value={resale} onChange={setResale} />
<Field label="Surface (m²)" value={surface} onChange={setSurface} />
<Field label="Travaux estimés (€)" value={works} onChange={setWorks} />
<Pressable style={styles.btn} onPress={() => router.push('/field')}>
<Text style={styles.btnText}>Field visit checklist</Text>
</Pressable>
<Pressable
style={[styles.btn, styles.btnGhost]}
onPress={() =>
void sharePurchaseOfferPdf({
propertyTitle: 'Bien cible',
address: 'À compléter',
maxBuyPriceEur: fin.maxBuyingPriceEur,
})
}
>
<Text style={styles.btnTextGhost}>One-click offer (PDF)</Text>
</Pressable>
<Text style={styles.foot}>
Agents SCOUT / ENGINEER / APIs : brancher Edge Functions + clés serveur.
Dossier checklist : id « {DEMO_PROPERTY_ID} ».
</Text>
</ScrollView>
);
}
function Field({
label,
value,
onChange,
}: {
label: string;
value: string;
onChange: (s: string) => void;
}) {
return (
<View style={{ marginBottom: 12 }}>
<Text style={styles.lab}>{label}</Text>
<TextInput
keyboardType="decimal-pad"
value={value}
onChangeText={onChange}
style={styles.inp}
placeholderTextColor={colors.muted}
/>
</View>
);
}
const styles = StyleSheet.create({
scroll: { padding: 16, paddingBottom: 48 },
banner: { borderRadius: 16, padding: 18, marginBottom: 20 },
bannerBad: { backgroundColor: '#3a121c' },
bannerOk: { backgroundColor: '#0f2a1c' },
bannerTitle: { color: colors.text, fontSize: 28, fontWeight: '900' },
bannerSub: { color: colors.muted, marginTop: 8, lineHeight: 20 },
h: { color: colors.text, fontSize: 16, fontWeight: '700', marginBottom: 10 },
lab: { color: colors.muted, fontSize: 12, marginBottom: 6 },
inp: {
borderWidth: 1,
borderColor: colors.border,
borderRadius: 10,
padding: 12,
color: colors.text,
backgroundColor: colors.card,
},
btn: {
backgroundColor: colors.accent,
paddingVertical: 14,
borderRadius: 12,
alignItems: 'center',
marginTop: 10,
},
btnGhost: {
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: colors.border,
},
btnText: { color: '#fff', fontWeight: '700', fontSize: 16 },
btnTextGhost: { color: colors.text, fontWeight: '700', fontSize: 16 },
foot: { color: colors.muted, marginTop: 24, fontSize: 12, lineHeight: 18 },
});