import json

IN = '/var/www/html/peta/storage/app/propertylab-catalogue-match/review/v_rest_01_in.jsonl'
OUT = '/var/www/html/peta/storage/app/propertylab-catalogue-match/review/v_rest_01_out.jsonl'

S, P, D, U = 'SAME', 'PART_OF', 'DIFFERENT', 'UNSURE'
RC, CR = 'R_in_C', 'C_in_R'

# case: (verdict, cand, confidence, relation, reason, web)
dec = {
'v0776': (U, 3, 40, None, "Many Taman Orkid in Johor, no district; Kluang one's price fits, geocode sits by Simpang Renggam's.", False),
'v0780': (S, 0, 92, None, "Distinctive name; web confirms Permata Hill Park is in Sungai Petani (Kuala Muda), listings ~RM458k.", True),
'v0784': (S, 0, 95, None, "Exact name, Klang; median price identical (384k).", False),
'v0788': (P, 0, 55, CR, "R is the Wakaf Tapai locality; Taman Tapai Indah is a new estate 1.3 km away within it.", False),
'v0792': (P, 0, 85, CR, "C is Fasa 2 of Taman Nilam Sari, Seremban; only NS namesake, 637 m.", False),
'v0796': (P, 0, 85, CR, "C is Fasa 2A of Taman Dato Ahmad Said, Ipoh; same Kinta district.", False),
'v0800': (S, 0, 82, None, "Exact unique name, 126 m; C median diluted by flats (580k vs 950k).", False),
'v0804': (S, 0, 68, None, "Exact name in KL, 460 m, but C median double R's (1.22m vs 600k).", False),
'v0808': (P, 0, 90, RC, "Tangkak Jaya 7 is a numbered phase of Taman Tangkak Jaya; 263 m, Tangkak district.", False),
'v0812': (S, 0, 72, None, "Taman Desa Serdang landed row (k1 is flats); name and place match, C price looks stale.", False),
'v0816': (U, None, 30, None, "No state; Penang namesake price far off (325k vs 625k), KL namesake unpriced.", False),
'v0820': (S, 0, 90, None, "Exact name, Puchong; price agrees (365k vs 400k); k1 is a townhouse sub-part.", False),
'v0824': (S, 0, 85, None, "Unique enclave name, 57 m; C bungalow median higher (4.8m vs 2.7m).", False),
'v0828': (S, 0, 90, None, "Exact name, low-cost houses; price agrees (135k vs 150k).", False),
'v0832': (S, 0, 82, None, "Township row, Kota Tinggi, 504 m; C median looks stale (247k vs 585k).", False),
'v0836': (S, 0, 92, None, "Web: Taman Sutera Indah is in Kluang, off Jalan Mersing; 71 m, price 465k vs 480k.", True),
'v0840': (S, 0, 82, None, "Same locality, PJ Section 18; C median stale (700k vs 1.6m).", False),
'v0844': (S, 0, 88, None, "Exact name, USJ Subang; price agrees (605k vs 520k).", False),
'v0848': (S, 0, 80, None, "Exact name, 494 m in Shah Alam; no C price to cross-check.", False),
'v0852': (S, 1, 80, None, "Exact name Taman Mewah Baru, Klang; R point snapped to Taman Mewah; price 0.66x.", False),
'v0856': (D, None, 82, None, "R is Office/SOHO; candidates are residential condos in Penang/Selangor.", False),
'v0860': (D, None, 70, None, "R is landed Taman Bukit Subang; candidates are apartment blocks or other estates.", False),
'v0864': (S, 2, 90, None, "Exact name and mukim Batang Berjuntai; price 140k vs 165k; C coordinates misplaced.", False),
'v0868': (U, 2, 40, None, "Generic 'Kinrara housing' scheme; could be Bandar Kinrara or a sub-estate.", False),
'v0872': (S, 1, 85, None, "Exact name, Batu Pahat; price 165k vs 180k; R point snapped to Kluang's Taman Anggerik.", False),
'v0876': (S, 0, 78, None, "Exact name, Skudai; price conflicts (455k vs 788k).", False),
'v0880': (P, 0, 90, RC, "Serom Utama 2 is a phase of Taman Serom Utama; 576 m, mukim Serom.", False),
'v0884': (U, 0, 45, None, "No state; R geocoded near Perlis; Skudai's Taman Sri Pulai price matches (310k vs 308k).", False),
'v0888': (S, 0, 90, None, "Exact name, price 500k vs 510k; web places Jeram estates in Muar; C's JB label wrong.", True),
'v0892': (S, 0, 85, None, "Both Butterworth town locality rows; C median odd/stale.", False),
'v0896': (D, None, 75, None, "Web: My Loft is Block D; C rows are SOHO M Tower, SOS block, or JB.", True),
'v0900': (S, 0, 90, None, "Exact name, Sepang; price agrees (340k vs 380k).", False),
'v0904': (U, 0, 45, None, "Many Kampung Tengah in Johor; no district; Segamat price far off.", False),
'v0908': (S, 0, 75, None, "Exact name, no state; geocoded 159 m; price 310k vs 368k.", False),
'v0912': (S, 1, 78, None, "Taman Bukit Mewah JB landed row, 3.7 km; price 420k vs 535k; k0 is flats.", False),
'v0916': (S, 1, 95, None, "Exact name, Batu Pahat; price identical (614k vs 614.2k).", False),
'v0920': (P, 0, 85, RC, "R is Fasa 7 of Taman MH Permatang Badak, Kuantan; 765 m.", False),
'v0924': (P, 0, 80, RC, "R is Taman Sri Wangi inside Shah Alam Section 36 locality; Kapar namesakes differ.", False),
'v0928': (S, 0, 88, None, "Tanjak alias matches Taman Tanjak, KKB; 204 m, price 260k vs 230k.", False),
'v0932': (S, 0, 58, None, "Exact name but no state; only Muar namesake, price 1.5x.", False),
'v0936': (S, 0, 88, None, "Exact name, Reflexion alias; 393 m, price 1.45m vs 1.55m.", False),
'v0940': (P, 0, 90, RC, "Nada 5 is a phase of Nada Alam, Mantin; 186 m.", False),
'v0944': (S, 0, 75, None, "Exact name, Nibong Tebal; C price far above low-cost R (330k vs 120k).", False),
'v0948': (P, 0, 82, RC, "R is Fasa 5 of Taman MH Batu Hitam, Kuantan.", False),
'v0952': (S, 0, 75, None, "Exact name in NS; no district; price 465k vs 700k.", False),
'v0956': (S, 0, 82, None, "Exact name, Batu 9 Cheras (mukim Cheras); price 1.2m vs 742k.", False),
'v0960': (P, 0, 82, RC, "Orkid is a sub-part of Taman Klang Utama; 526 m, price 470k vs 400k.", False),
'v0964': (S, 0, 80, None, "Exact name (Sri/Seri), 77 m, Penang; price 680k vs 1.04m.", False),
'v0968': (P, 0, 55, RC, "Tambahan is the extension of Kampung Melayu Rasa; may be a separate village.", False),
'v0972': (P, 0, 90, RC, "Web confirms Taman Sungai Abong Permai 2 in Muar, a phase of Sg Abong Permai; 1.1 km.", True),
'v0976': (S, 0, 85, None, "Exact name, Klang district, 363 m; price 650k vs 425k.", False),
'v0980': (S, 0, 70, None, "Batu 9 Cheras new village; no district, price 350k vs 505k.", False),
'v0984': (S, 0, 92, None, "Web: 10-unit 3-storey townhouse block, listings ~1.6-1.8m; 215 m.", True),
'v0988': (P, 0, 85, CR, "C is Taman PD Teknik 3, a phase of R; 77 m, Port Dickson.", False),
'v0992': (S, 1, 82, None, "Exact name, Pontian; R geocoded in Pontian; no C price.", False),
'v0996': (P, 0, 88, CR, "C is Taman Wawasan II at Kota Sarang Semut; 147 m.", False),
'v1000': (S, 0, 72, None, "C is the Fraser Business Park commercial development itself; 144 m; type label wrong.", False),
'v1004': (S, 0, 88, None, "Harmoni Vista, Pagoh, 5 m; only Johor namesake; no C price.", False),
'v1008': (P, 0, 88, RC, "R is phase II of Taman Air Putih; price 535k vs 540k.", False),
'v1012': (S, 1, 85, None, "Taman Maju, Batu Pahat town; price 380k vs 333k; R point snapped to Parit Raja.", False),
'v1016': (P, 0, 85, RC, "Taman Desa Suria lies within the SS25 locality row; 310 m.", False),
'v1020': (S, 1, 65, None, "Exact name; C 'Bekok' label/coords off; Taman Kahang Baru is a different estate.", False),
'v1024': (S, 0, 72, None, "Exact name, Sungai Buloh, 256 m; price 500k vs 350k.", False),
'v1028': (P, 0, 65, CR, "Taman Mutiara Tanjung is an estate inside Bandar Batu Kawan township.", False),
'v1032': (S, 0, 85, None, "Both Sungai Penchala locality rows; price 1.75m vs 1.38m.", False),
'v1036': (S, 0, 72, None, "Exact name, price 230k vs 235k; but Sepang namesake exists.", False),
'v1040': (S, 0, 62, None, "Only Selangor Kampung Air Panas (Kerling); price 136k vs 62.5k.", False),
'v1044': (S, 0, 90, None, "Taman Johor, JB (Tebrau); price 499.5k vs 445k.", False),
'v1048': (P, 0, 90, RC, "Nada 2 is a phase of Nada Alam, Mantin; 410 m.", False),
'v1052': (S, 0, 90, None, "Exact name, KL; price 350k vs 364k.", False),
'v1056': (S, 0, 78, None, "Taman Seri Bakri 3 = Sri Bakri III, Muar; C coords ~6.5 km off.", False),
'v1060': (S, 0, 88, None, "Distinctive exact name, Perak; no C price.", False),
'v1064': (S, 0, 85, None, "Only Johor Taman Sri Rawang (Sungai Mati); no C price.", False),
'v1068': (U, None, 20, None, "Bare 'Saujana', no state; point far from every namesake.", False),
'v1072': (D, None, 80, None, "Web: Taman Evergreen is a 14-storey apartment block; R is landed on Jalan Evergreen.", True),
'v1076': (S, 2, 90, None, "Exact name, Hulu Langat; price 582.5k vs 550k.", False),
'v1080': (D, None, 65, None, "Road-named scheme (900k) vs a new terrace phase; no price to link.", False),
'v1084': (P, 0, 80, RC, "Orkid is a sub-part of Taman Klang Utama; 909 m, price 509.5k vs 400k.", False),
'v1088': (P, 0, 82, RC, "R is Fasa 1 of Taman Pokok Mangga, Melaka; 270 m.", False),
'v1092': (S, 0, 75, None, "Road-named but 42 m from Taman Kim Kee; price 1.06m vs 861k.", False),
'v1096': (P, 0, 80, RC, "Residence 3A is a precinct of Bandar Springhill, Port Dickson.", False),
'v1100': (S, 0, 88, None, "Exact unique name, 1.1 km; C median stale (4.0m vs 9.4m).", False),
'v1104': (P, 0, 62, RC, "Jalan Kampung Jawa lots lie within the Kampung Jawa, Klang locality.", False),
'v1108': (P, 2, 80, CR, "R is the Bukit Banyan township; C rows are its sub-projects.", False),
'v1112': (S, 1, 90, None, "Lakeview in Bertam Perdana 2 = Bertam Lakeview; 237 m, price 599k vs 569k.", False),
'v1116': (S, 1, 72, None, "Bracket names the Cahaya Alam project; price 1.15m vs 1.03m; coords off.", False),
'v1120': (S, 1, 88, None, "Sanctuary Garden alias matches; 221 m, price 750k vs 851k.", False),
'v1124': (P, 0, 72, RC, "Aston Barat (west) is a section of Taman Aston, like Aston Park East.", False),
'v1128': (S, 0, 80, None, "C row is Temasya Industrial Park itself; 309 m.", False),
'v1132': (U, 0, 45, None, "No state; several Taman Kasih; Kajang price 0.77x.", False),
'v1136': (U, 0, 40, None, "Many Taman Emas in Johor; no district.", False),
'v1140': (P, 0, 88, RC, "Nagaria 3 is a phase of Taman Nagaria; price 220k vs 240k.", False),
'v1144': (S, 0, 85, None, "Estate named by its road, 697 m, Penang; price 315k vs 320k.", False),
'v1148': (P, 0, 70, RC, "An estate inside the Puchong locality row.", False),
'v1152': (D, None, 55, None, "Jalan Jesselton road scheme vs Taman Jesselton; price 10m vs 6.45m.", False),
'v1156': (S, 1, 65, None, "Web: Ulu Yam has two villages, Lama and Bharu; new village = Ulu Yam Baharu; price 1.6x.", True),
'v1160': (S, 0, 90, None, "Exact name, 2.4 km; price 488k vs 500k; Tangkak was part of Muar.", False),
'v1164': (S, 0, 78, None, "Exact name, 78 m, Nibong Tebal; C price stale (190k vs 350k).", False),
'v1168': (S, 0, 85, None, "Alias Taman Tronoh Baru exact; Kinta district.", False),
'v1172': (S, 0, 82, None, "Only Johor Taman Denai Alam; price 535k vs 672k.", False),
'v1176': (S, 0, 85, None, "Exact name, Klang/Kapar; price 550k vs 412k.", False),
'v1180': (S, 1, 80, None, "Exact name, Pontian, 6.9 km; price 575k vs 322k.", False),
'v1184': (S, 0, 80, None, "Exact name, Penang; C median includes flats.", False),
'v1188': (S, 0, 85, None, "Bandar Baru Salak Tinggi township row; 824 m.", False),
'v1192': (S, 0, 55, None, "Exact name, 287 m, but R price 4x C (6.25m vs 1.5m).", False),
'v1196': (S, 0, 78, None, "Exact name, Penang, 318 m; price 356k vs 300k.", False),
'v1200': (D, None, 60, None, "R in Serendah (Hulu Selangor); web puts C's Taman Mawar in Rawang town; k0 is flats.", True),
'v1204': (S, 0, 85, None, "Taman Prima Impian, KL, 242 m; C price clearly bad (100k).", False),
'v1208': (S, 0, 85, None, "Exact name, Batu Pahat, 686 m; k1 is its phase.", False),
'v1212': (S, 1, 88, None, "Seksyen 4 Puncak Tamu exact; price 470k vs 445k; C coords wrong.", False),
'v1216': (S, 0, 75, None, "Exact name, Klang; price 722k vs 465k.", False),
'v1220': (S, 0, 85, None, "Exact name, Kuala Selangor; price 240k vs 374k.", False),
'v1224': (P, 3, 72, CR, "C rows are phases of Taman Bertam Heights.", False),
'v1228': (S, 0, 85, None, "Distinctive exact name, Sepang.", False),
'v1232': (S, 0, 68, None, "Taman Emas Jaya, Batu Pahat; price 950k vs 499k.", False),
'v1236': (S, 0, 88, None, "Exact new-village name, Kulai; price 190k vs 130k.", False),
'v1240': (S, 0, 65, None, "Taman Seri, Sungai Besar; price 258k vs 550k.", False),
'v1244': (S, 0, 85, None, "Exact name (Sri/Seri), Selangor; price 386.5k vs 455k.", False),
'v1248': (S, 0, 68, None, "Exact name, equal round price 300k; no state.", False),
'v1252': (P, 0, 68, CR, "C is Fasa 2 of the Bukit Bay Route estate; 679 m.", False),
'v1256': (U, 0, 45, None, "No state; Cheras and Sabak Bernam namesakes; neither price agrees.", False),
'v1260': (S, 0, 85, None, "Taman Perwira, Gombak, 452 m; price 770k vs 500k.", False),
'v1264': (S, 0, 78, None, "Exact name Taman Seri Changgang 1; Banting.", False),
'v1268': (P, 0, 88, RC, "Web: Aman is one of Serenia City's five precincts; 1.4 km.", True),
'v1272': (S, 1, 92, None, "Exact name, landed row; price 495k vs 500k.", False),
'v1276': (P, 0, 88, RC, "Precinct 2 of Taman Mutiara Utama, Skudai; 307 m.", False),
'v1280': (S, 2, 92, None, "Aman Putri, Sungai Buloh; price identical 700k.", False),
'v1284': (S, 0, 85, None, "Senai new village, 171 m; price 300k vs 180k.", False),
'v1288': (S, 0, 85, None, "Exact name, Benut; price 300k vs 235k.", False),
'v1292': (S, 1, 78, None, "Ceria Residences, Cyberjaya, 67 m; R category landed but project is a condo.", False),
'v1296': (S, 0, 72, None, "Slash alias names Tiara Kemensah; 262 m; C median far below (1.5m vs 5m).", False),
'v1300': (P, 0, 65, RC, "Taman Pekan Meru inside the Pekan Meru town row.", False),
'v1304': (P, 0, 85, RC, "Gunung Emas II is a phase of Taman Gunung Emas, Tangkak.", False),
'v1308': (U, 0, 45, None, "No state; Taman Ria Jaya exists in several towns.", False),
'v1312': (P, 0, 85, RC, "Rimba Jaya I is a phase of Taman Rimba Jaya; 296 m.", False),
'v1316': (S, 0, 80, None, "Exact name, Segamat district; C coords 47 km off.", False),
'v1320': (S, 0, 92, None, "Web: Mansion One is the former Northam Tower (k1 duplicate); 14 m.", True),
'v1324': (S, 0, 70, None, "Exact name Kampung Budiman; Alam Budiman is a different estate.", False),
'v1328': (S, 1, 88, None, "Taman Sri Wangi, Kapar (Klang); price 360k vs 380k.", False),
'v1332': (S, 0, 65, None, "Exact spelling Putrimas; a Puterimas variant also in Kluang.", False),
'v1336': (S, 0, 82, None, "Sungai Buloh new village, 288 m; C median stale.", False),
'v1340': (P, 0, 85, CR, "C is Phase 2 of Santalia Villa; 227 m.", False),
'v1344': (P, 0, 85, RC, "R is Fasa 5 of Taman MH Permatang Badak; 765 m.", False),
'v1348': (S, 0, 78, None, "Distinctive name, 148 m; price 290k vs 244k.", False),
'v1352': (S, 1, 72, None, "Taman Sri/Seri Cempaka, Muar; price 734k vs 517.5k.", False),
'v1356': (S, 0, 82, None, "Exact name, Batang Berjuntai.", False),
'v1360': (P, 0, 85, RC, "Hijrah 5 is a phase of Taman Hijrah, Kangar; 536 m.", False),
'v1364': (S, 0, 65, None, "Same Pogoh point; price 110k vs 127.5k; C name adds 'Sri'.", False),
'v1368': (S, 0, 85, None, "PJ Section 9 (not Kota Damansara's); C median stale.", False),
'v1372': (S, 0, 85, None, "Setia Alamsari township row; price 1.34m vs 837k.", False),
'v1376': (S, 1, 60, None, "Exact name; no state; price 300k vs 373k.", False),
'v1380': (P, 0, 75, RC, "Putra Indah section within Taman Serdang Raya; 811 m.", False),
'v1384': (S, 0, 88, None, "Exact name; price 1.8m vs 1.5m.", False),
'v1388': (P, 0, 78, RC, "Sanctuary Village is a sub-project of Taman Santuari; 108 m.", False),
'v1392': (S, 1, 95, None, "Taman Daya, JB; price 450k vs 449k.", False),
'v1396': (S, 0, 58, None, "Exact name, Sabak Bernam; price 100k vs 382k.", False),
'v1400': (P, 0, 85, CR, "C is Taman Bukit Tingkat Jaya 3, a phase; 368 m.", False),
'v1404': (S, 0, 90, None, "Maharani = Muar; price 440k vs 402.5k.", False),
'v1408': (S, 0, 72, None, "Exact name, Jasin, 450 m; C labelled flat new launch.", False),
'v1412': (S, 0, 85, None, "Exact name, Rawang, 70 m; C median includes flats.", False),
'v1416': (P, 0, 85, RC, "Pertama Indah III is a phase of Taman Pertama Indah, Muar; 531 m.", False),
'v1420': (U, 0, 45, None, "Web: Mutiara Keruing (2018 clusters) and M3/Keruing Residen (2024 terraces) are neighbours; R 705k equals M3's launch price.", True),
'v1424': (P, 0, 80, RC, "Residence 7 is a precinct of Bandar Springhill.", False),
'v1428': (P, 0, 85, RC, "R is Fasa 2 of Krai Central Park; 247 m.", False),
'v1432': (U, None, 40, None, "Rawang and Ampang namesakes; no district; prices similar.", False),
'v1436': (P, 0, 88, RC, "Fasa 7 of Eco Horizon, Batu Kawan; 124 m.", False),
'v1440': (S, 0, 90, None, "Exact name, Skudai; price identical 380k.", False),
'v1444': (P, 0, 85, RC, "Bestari Indah 1 is a phase of Taman Bestari Indah; 348 m.", False),
'v1448': (D, None, 68, None, "R is Office/SOHO; C rows are residential townships.", False),
'v1452': (S, 0, 88, None, "Taman Gembira, Klang; price 480k vs 550k.", False),
'v1456': (P, 2, 78, CR, "C is Taman Mengkuang Jaya 3, a phase.", False),
'v1460': (P, 0, 80, RC, "Phase II of Taman Seri Sekinchan; 997 m.", False),
'v1464': (S, 0, 85, None, "Exact name, 50 m, Muar; C price conflicts (350k vs 175k).", False),
'v1468': (S, 0, 88, None, "C 'Taman Alma' = phases 1-3 (4-6 separate); price 395k vs 375k.", False),
'v1472': (S, 0, 80, None, "Neo Damansara is itself a SOHO development; 38 m.", False),
'v1476': (P, 0, 88, RC, "Palma Sands is a precinct of Gamuda Cove.", False),
'v1480': (P, 1, 80, CR, "C rows are Presint 2 phases of Bandar Scientex Jasin.", False),
'v1484': (S, 1, 78, None, "Bracket names the Laman Oakleaf project; 577 m, price 600k vs 695k.", False),
'v1488': (S, 0, 90, None, "Exact name, Kota Tinggi; price 147.5k vs 142.5k.", False),
'v1492': (S, 1, 88, None, "PJ Section 10 (k0 is Kota Damansara); price 798k vs 640k.", False),
'v1496': (S, 0, 88, None, "Continental Park alias exact; price 640k vs 620k; type label differs.", False),
'v1500': (S, 0, 85, None, "C row is Amcorp Mall itself; R shop lots.", False),
'v1504': (D, None, 55, None, "R is Danga Bay locality SOHO; candidates are residential projects.", False),
'v1508': (U, None, 40, None, "Two Kedah Taman Damar (Kubang Pasu, Pokok Sena); no district.", False),
'v1512': (P, 0, 78, RC, "Fasa 1 of Taman Kenangan, Kulim; 1.4 km.", False),
'v1516': (S, 0, 62, None, "Exact name, 451 m; C is a new-launch lot; no state.", False),
'v1520': (S, 0, 85, None, "C row is Temasya Industrial Park itself.", False),
'v1524': (S, 1, 90, None, "Taman Sri Raya, Batu 9 Cheras; price 360k vs 365k.", False),
'v1528': (P, 0, 82, RC, "Sri Bekok II is a phase of Taman Sri Bekok.", False),
'v1532': (D, None, 58, None, "Taman vs Kampung Sungai Udang: separate schemes despite 782 m and similar price.", False),
'v1536': (S, 0, 72, None, "Exact name, Klang; no C price.", False),
'v1540': (S, 0, 90, None, "Exact name, 129 m; price 220k vs 223k; condo label wrong.", False),
'v1544': (P, 0, 88, RC, "Fasa 3 of Sunway Cassia; 421 m.", False),
'v1548': (S, 2, 65, None, "Exact name Taman Wangi, Klang (Jalan Tepi Sungai); price 485k vs 310k.", False),
'v1552': (S, 0, 82, None, "Taman Megah, Kluang, 146 m; price 165k vs 240k.", False),
'v1556': (S, 0, 90, None, "Exact name, Sepang district; price 450k vs 420k.", False),
'v1560': (S, 1, 92, None, "Taman Dagang, Klang; identical price 410k.", False),
'v1564': (S, 0, 92, None, "Web: Sunrise Gurney is a commercial-strata SOHO/serviced residence; 26 m.", True),
'v1568': (S, 0, 85, None, "Exact name, Manjung (Sitiawan).", False),
'v1572': (S, 0, 82, None, "Exact new-village name, 68 m.", False),
'v1576': (P, 0, 70, RC, "Lot scheme inside the mukim Labu locality row.", False),
'v1580': (S, 0, 55, None, "Exact name; price conflicts hugely (1.09m vs 205k); C type unknown.", False),
'v1584': (S, 0, 85, None, "C row is the Tangkak industrial area itself; 168 m.", False),
'v1588': (S, 0, 70, None, "Exact name, Johor; price 142.5k vs 260k.", False),
'v1592': (U, None, 30, None, "Bare name, no state; four namesakes, R point near none.", False),
'v1596': (S, 1, 88, None, "Exact name, Klang; price 425k vs 400k; R point snapped to Taman Mewah.", False),
'v1600': (P, 1, 80, CR, "C is Taman Kangkar Jaya 2; 145 m, price 205k vs 210k.", False),
'v1604': (S, 0, 85, None, "Web: Taman Kuso is in Kota Tinggi; C's Masai label wrong; low-cost fits.", True),
'v1608': (S, 0, 75, None, "C row is the business centre itself; 4.7 km; price conflicts.", False),
'v1612': (P, 0, 82, CR, "C is Taman Semabok Jaya 2, a phase.", False),
'v1616': (P, 1, 82, RC, "Fasa 4 of Taman Alma Jaya (not Taman Alma).", False),
'v1620': (S, 0, 80, None, "C combined Soga Sutera/Soga Perdana row, 332 m.", False),
'v1624': (S, 0, 65, None, "Same named area, 168 m; R landed category odd.", False),
'v1628': (S, 0, 88, None, "Exact name, Batu Pahat; price 160k vs 122.5k.", False),
'v1632': (S, 0, 85, None, "Sungai Long township row; 311 m.", False),
'v1636': (S, 0, 85, None, "Only Selangor namesake (Klang), 647 m; price 495k vs 600k.", False),
'v1640': (S, 0, 75, None, "Exact name, 166 m, Batu Pahat; both prices odd.", False),
'v1644': (S, 1, 90, None, "Exact name; price 515k vs 540k.", False),
'v1648': (S, 1, 92, None, "Empire Soho is the SOHO part; price 490k vs 457.5k.", False),
'v1652': (S, 1, 80, None, "Taman Wawasan, Tangkak exact; C coords 18 km off.", False),
'v1656': (S, 0, 75, None, "Exact name, no state; price 645k vs 550k.", False),
'v1660': (P, 0, 72, CR, "C rows are sub-villages of Kampung Paya Jaras.", False),
'v1664': (S, 1, 82, None, "Exact name, Rawang; identical 1.0m price; type label differs.", False),
'v1668': (S, 0, 62, None, "Bandar Baru Lahat Eco-city likely the same scheme; 1.8 km.", False),
'v1672': (P, 0, 85, RC, "Mida 3 is a phase of Taman Mida, Segamat.", False),
'v1676': (S, 0, 85, None, "Only Selangor namesake (Semenyih); price 352.5k vs 407.5k.", False),
'v1680': (S, 2, 75, None, "Exact name, Rengam; price 100k vs 200k.", False),
'v1684': (S, 0, 82, None, "Exact name, Skudai; price 292.5k vs 389k.", False),
'v1688': (S, 0, 85, None, "Exact name, Gelugor; C median includes flats.", False),
'v1692': (U, None, 20, None, "Very generic name, no state.", False),
'v1696': (P, 0, 80, CR, "C is phase 3 of Taman Dahlia RG; 139 m.", False),
'v1700': (P, 0, 90, RC, "Fasa 1 of Taman Koperasi Polis, Batu; price 425k vs 430k.", False),
'v1704': (U, 0, 45, None, "No state; three Taman Senangin; Penang one 0.85x.", False),
'v1708': (S, 0, 80, None, "Exact name, mukim Serom; C price stale.", False),
'v1712': (S, 0, 80, None, "Taman Teratai, Sungai Ramal; 194 m; price 1.4m vs 620k.", False),
'v1716': (S, 1, 90, None, "Exact name and town Sabak Bernam; price 210k vs 168k.", False),
'v1720': (P, 2, 55, CR, "Taman Sejati 5 near R point, price 400k vs 412.5k; another Taman Sejati 4.5 km away.", False),
'v1724': (S, 0, 62, None, "Landed Kiara View (semi-D); but condo namesake has exact 2.5m price.", False),
'v1728': (S, 0, 75, None, "Exact name, Jeram; C price conflicts.", False),
'v1732': (S, 1, 85, None, "Exact name, 216 m; price 680k vs 590k.", False),
'v1736': (P, 0, 85, CR, "C is Taman Keladan Impian 2, a phase.", False),
'v1740': (S, 0, 78, None, "Rini Homes 8 in Mutiara Rini; C adds phase code.", False),
'v1744': (P, 0, 55, CR, "Web: Hijayu 2 Resort Homes = terraces/semi-Ds; Resort Villa likely its detached part.", True),
'v1748': (D, None, 55, None, "Taman vs Kampung Rawang Tin; separate schemes despite proximity.", False),
'v1752': (S, 0, 82, None, "Exact name, Johor; price 180k vs 130k.", False),
'v1756': (P, 0, 78, RC, "Fasa 3 of Taman Kenangan, Kulim.", False),
'v1760': (S, 0, 55, None, "Sri Seputih = Sri Seputeh, Seputeh; type/price conflict.", False),
}

rows = [json.loads(l) for l in open(IN)]
ids = [r['case'] for r in rows]
assert len(ids) == len(set(ids)) == 247, (len(ids), len(set(ids)))
missing = [i for i in ids if i not in dec]
extra = [k for k in dec if k not in ids]
assert not missing, missing
assert not extra, extra

problems = []
out = []
for r in rows:
    cid = r['case']
    v, cand, conf, rel, reason, web = dec[cid]
    ks = [c['k'] for c in r['catalogue_candidates']]
    if v not in (S, P, D, U): problems.append((cid, 'verdict'))
    if cand is not None and cand not in ks: problems.append((cid, 'cand not in candidates', cand, ks))
    if v in (S, P) and cand is None: problems.append((cid, 'SAME/PART_OF needs cand'))
    if v == D and cand is not None: problems.append((cid, 'DIFFERENT must have null cand'))
    if v == P and rel not in (RC, CR): problems.append((cid, 'PART_OF needs relation'))
    if v != P and rel is not None: problems.append((cid, 'relation only for PART_OF'))
    if not (0 <= conf <= 100): problems.append((cid, 'conf'))
    if len(reason.split()) > 20: problems.append((cid, 'reason words', len(reason.split())))
    out.append({"case": cid, "verdict": v, "cand": cand, "confidence_pct": conf,
                "relation": rel, "reason": reason, "web": web})

if problems:
    for p in problems: print('PROBLEM', p)
    raise SystemExit(1)

with open(OUT, 'w') as f:
    for o in out:
        f.write(json.dumps(o, ensure_ascii=False) + '\n')

from collections import Counter
print('written', len(out))
print(Counter(o['verdict'] for o in out))
print('>=90:', sum(1 for o in out if o['confidence_pct'] >= 90))
print('>=90 by verdict:', Counter(o['verdict'] for o in out if o['confidence_pct'] >= 90))
print('web cases:', sum(1 for o in out if o['web']))
print('PART_OF relations:', Counter(o['relation'] for o in out if o['verdict'] == 'PART_OF'))
